From cf8adf02815ca1348fc9a2f350158b6a2d8c627a Mon Sep 17 00:00:00 2001 From: Simon McEnlly Date: Thu, 2 Sep 2021 14:45:41 +1000 Subject: [PATCH 0001/2210] testing: add new API `sortText` property to TestItem; if populated will be used instead of `label` for sorting items in Test Explorer tree and list view --- src/vs/vscode.d.ts | 6 ++++++ src/vs/workbench/api/common/extHostTestingPrivateApi.ts | 5 ++++- src/vs/workbench/api/common/extHostTypeConverters.ts | 3 +++ .../contrib/testing/browser/explorerProjections/index.ts | 4 ++++ .../contrib/testing/browser/testingExplorerView.ts | 2 +- src/vs/workbench/contrib/testing/common/testCollection.ts | 1 + 6 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 067cf63be16ef..8825c3e246e16 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -14292,6 +14292,12 @@ declare module 'vscode' { */ description?: string; + /** + * Optional sortText to sort the test items by. If sort text exists on a + * {@link TestItem} then it is used instead of {@link TestItem.label}. + */ + sortText?: string; + /** * Location of the test item in its {@link uri}. * diff --git a/src/vs/workbench/api/common/extHostTestingPrivateApi.ts b/src/vs/workbench/api/common/extHostTestingPrivateApi.ts index 270de0bd11848..26c7081cce3e0 100644 --- a/src/vs/workbench/api/common/extHostTestingPrivateApi.ts +++ b/src/vs/workbench/api/common/extHostTestingPrivateApi.ts @@ -95,7 +95,7 @@ const testItemPropAccessor = ( }; }; -type WritableProps = Pick; +type WritableProps = Pick; const strictEqualComparator = (a: T, b: T) => a === b; @@ -107,6 +107,7 @@ const propComparators: { [K in keyof Required]: (a: vscode.TestIt }, label: strictEqualComparator, description: strictEqualComparator, + sortText: strictEqualComparator, busy: strictEqualComparator, error: strictEqualComparator, canResolveChildren: strictEqualComparator, @@ -129,6 +130,7 @@ const makePropDescriptors = (api: IExtHostTestItemApi, label: string): { [K in k range: testItemPropAccessor(api, 'range', undefined, propComparators.range), label: testItemPropAccessor(api, 'label', label, propComparators.label), description: testItemPropAccessor(api, 'description', undefined, propComparators.description), + sortText: testItemPropAccessor(api, 'sortText', undefined, propComparators.sortText), canResolveChildren: testItemPropAccessor(api, 'canResolveChildren', false, propComparators.canResolveChildren), busy: testItemPropAccessor(api, 'busy', false, propComparators.busy), error: testItemPropAccessor(api, 'error', undefined, propComparators.error), @@ -268,6 +270,7 @@ export class TestItemImpl implements vscode.TestItem { public range!: vscode.Range | undefined; public description!: string | undefined; + public sortText!: string | undefined; public label!: string; public error!: string | vscode.MarkdownString; public busy!: boolean; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 3e9b18aafa955..185af86bcc17e 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1685,6 +1685,7 @@ export namespace TestItem { tags: item.tags.map(t => TestTag.namespace(ctrlId, t.id)), range: Range.from(item.range) || null, description: item.description || null, + sortText: item.sortText || null, error: item.error ? (MarkdownString.fromStrict(item.error) || null) : null, }; } @@ -1703,6 +1704,7 @@ export namespace TestItem { canResolveChildren: false, busy: false, description: item.description || undefined, + sortText: item.sortText || undefined, }; } @@ -1711,6 +1713,7 @@ export namespace TestItem { const testItem = new TestItemImpl(testId.controllerId, testId.localId, item.label, URI.revive(item.uri)); testItem.range = Range.to(item.range || undefined); testItem.description = item.description || undefined; + testItem.sortText = item.sortText || undefined; return testItem; } diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts index 932c2a40de578..734d3d90d511a 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts @@ -115,6 +115,10 @@ export class TestItemTreeElement implements IActionableTestTreeElement { return this.test.item.description; } + public get sortText() { + return this.test.item.sortText; + } + /** * Whether the node's test result is 'retired' -- from an outdated test run. */ diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 7fcd0779b87f9..00b81adb49627 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -948,7 +948,7 @@ class TreeSorter implements ITreeSorter { } } - return a.label.localeCompare(b.label); + return (a.sortText || a.label).localeCompare((b.sortText || b.label)); } } diff --git a/src/vs/workbench/contrib/testing/common/testCollection.ts b/src/vs/workbench/contrib/testing/common/testCollection.ts index 9646aefa17ae5..c4349e4e04d14 100644 --- a/src/vs/workbench/contrib/testing/common/testCollection.ts +++ b/src/vs/workbench/contrib/testing/common/testCollection.ts @@ -160,6 +160,7 @@ export interface ITestItem { range: IRange | null; description: string | null; error: string | IMarkdownString | null; + sortText: string | null; } export const enum TestItemExpandState { From 2d96ea48f62140ffdecb6cf32a6de924dec3e50b Mon Sep 17 00:00:00 2001 From: Mai-Lapyst Date: Fri, 12 Mar 2021 23:37:00 +0100 Subject: [PATCH 0002/2210] Adding default fileicon support to language contributions. Fixes #14662 --- .../common/services/languagesRegistry.ts | 16 +- src/vs/editor/common/services/modeService.ts | 2 + .../editor/common/services/modeServiceImpl.ts | 4 + src/vs/monaco.d.ts | 1 + .../mode/common/workbenchModeService.ts | 12 +- .../themes/browser/fileIconThemeData.ts | 370 +++++++++++------- .../themes/browser/workbenchThemeService.ts | 8 +- 7 files changed, 256 insertions(+), 157 deletions(-) diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index c734735a733fc..194afe1fac821 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -26,6 +26,7 @@ export interface IResolvedLanguage { extensions: string[]; filenames: string[]; configurationFiles: URI[]; + icons: string[]; } export class LanguagesRegistry extends Disposable { @@ -129,7 +130,8 @@ export class LanguagesRegistry extends Disposable { aliases: [], extensions: [], filenames: [], - configurationFiles: [] + configurationFiles: [], + icons: [] }; this._languages[langId] = resolvedLanguage; } @@ -227,6 +229,10 @@ export class LanguagesRegistry extends Disposable { if (lang.configuration) { resolvedLanguage.configurationFiles.push(lang.configuration); } + + if (lang.icon) { + resolvedLanguage.icons.push(lang.icon); + } } public isRegisteredMode(mimetypeOrModeId: string): boolean { @@ -275,6 +281,14 @@ export class LanguagesRegistry extends Disposable { return (language.mimetypes[0] || null); } + public getIconForMode(modeId: string): string | null { + if (!hasOwnProperty.call(this._languages, modeId)) { + return null; + } + const language = this._languages[modeId]; + return (language.icons[0] || null); + } + public extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): string[] { if (!commaSeparatedMimetypesOrCommaSeparatedIds) { return []; diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index 8c383913895b1..7b66fe0b7c1f5 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -19,6 +19,7 @@ export interface ILanguageExtensionPoint { aliases?: string[]; mimetypes?: string[]; configuration?: URI; + icon?: string; } export interface ILanguageSelection { @@ -39,6 +40,7 @@ export interface IModeService { getExtensions(alias: string): string[]; getFilenames(alias: string): string[]; getMimeForMode(modeId: string): string | null; + getIconForMode(modeId: string): string | null; getLanguageName(modeId: string): string | null; getModeIdForLanguageName(alias: string): string | null; getModeIdByFilepathOrFirstLine(resource: URI, firstLine?: string): string | null; diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 8698618c889f3..38db18e569cd3 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -96,6 +96,10 @@ export class ModeServiceImpl extends Disposable implements IModeService { return this._registry.getMimeForMode(modeId); } + public getIconForMode(modeId: string): string | null { + return this._registry.getIconForMode(modeId); + } + public getLanguageName(modeId: string): string | null { return this._registry.getLanguageName(modeId); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b58924637d593..972045b9d958a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6716,6 +6716,7 @@ declare namespace monaco.languages { aliases?: string[]; mimetypes?: string[]; configuration?: Uri; + icon?: string; } /** * A Monarch language definition diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index 7294d4617813c..642c326526733 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -26,6 +26,7 @@ export interface IRawLanguageExtensionPoint { aliases: string[]; mimetypes: string[]; configuration: string; + icon: string; } export const languagesExtPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ @@ -85,6 +86,10 @@ export const languagesExtPoint: IExtensionPoint = description: nls.localize('vscode.extension.contributes.languages.configuration', 'A relative path to a file containing configuration options for the language.'), type: 'string', default: './language-configuration.json' + }, + icon: { + description: nls.localize('vscode.extension.contributes.languages.icon', 'A contributed icon name to use as file icon, if no icon theme provides one for the language'), + type: 'string' } } } @@ -131,7 +136,8 @@ export class WorkbenchModeServiceImpl extends ModeServiceImpl { firstLine: ext.firstLine, aliases: ext.aliases, mimetypes: ext.mimetypes, - configuration: configuration + configuration: configuration, + icon: ext.icon }); } } @@ -229,6 +235,10 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec collector.error(nls.localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes')); return false; } + if (typeof value.icon !== 'undefined' && typeof value.icon !== 'string') { + collector.error(nls.localize('opt.icon', "property `{0}` can be omitted and must be of type `string`", 'icon')); + return false; + } return true; } diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 5b916d080be67..ff95f1d075841 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -11,8 +11,12 @@ import * as Json from 'vs/base/common/json'; import { ExtensionData, IThemeExtensionPoint, IWorkbenchFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IFileService } from 'vs/platform/files/common/files'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; -import { asCSSUrl } from 'vs/base/browser/dom'; +import { asCSSPropertyValue, asCSSUrl } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { getIconRegistry, IconDefaults } from 'vs/platform/theme/common/iconRegistry'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export class FileIconThemeData implements IWorkbenchFileIconTheme { @@ -42,27 +46,16 @@ export class FileIconThemeData implements IWorkbenchFileIconTheme { this.hidesExplorerArrows = false; } - public ensureLoaded(fileService: IFileService): Promise { - return !this.isLoaded ? this.load(fileService) : Promise.resolve(this.styleSheetContent); + public ensureLoaded(instantiationService: IInstantiationService): Promise { + return !this.isLoaded ? this.load(instantiationService) : Promise.resolve(this.styleSheetContent); } - public reload(fileService: IFileService): Promise { - return this.load(fileService); + public reload(instantiationService: IInstantiationService): Promise { + return this.load(instantiationService); } - private load(fileService: IFileService): Promise { - if (!this.location) { - return Promise.resolve(this.styleSheetContent); - } - return _loadIconThemeDocument(fileService, this.location).then(iconThemeDocument => { - const result = _processIconThemeDocument(this.id, this.location!, iconThemeDocument); - this.styleSheetContent = result.content; - this.hasFileIcons = result.hasFileIcons; - this.hasFolderIcons = result.hasFolderIcons; - this.hidesExplorerArrows = result.hidesExplorerArrows; - this.isLoaded = true; - return this.styleSheetContent; - }); + private load(instantiationService: IInstantiationService): Promise { + return instantiationService.createInstance(FileIconThemeLoader).load(this); } static fromExtensionTheme(iconTheme: IThemeExtensionPoint, iconThemeLocation: URI, extensionData: ExtensionData): FileIconThemeData { @@ -197,185 +190,258 @@ interface IconThemeDocument extends IconsAssociation { hidesExplorerArrows?: boolean; } -function _loadIconThemeDocument(fileService: IFileService, location: URI): Promise { - return fileService.readFile(location).then((content) => { - let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content.value.toString(), errors); - if (errors.length > 0) { - return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); - } else if (Json.getNodeType(contentValue) !== 'object') { - return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for file icons theme file: Object expected."))); - } - return Promise.resolve(contentValue); - }); -} - -function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, iconThemeDocument: IconThemeDocument): { content: string; hasFileIcons: boolean; hasFolderIcons: boolean; hidesExplorerArrows: boolean; } { +class FileIconThemeLoader { - const result = { content: '', hasFileIcons: false, hasFolderIcons: false, hidesExplorerArrows: !!iconThemeDocument.hidesExplorerArrows }; + constructor( + @IFileService private readonly fileService: IFileService, + @IModeService private readonly modeService: IModeService + ) { - if (!iconThemeDocument.iconDefinitions) { - return result; } - let selectorByDefinitionId: { [def: string]: string[] } = {}; - const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation); - function resolvePath(path: string) { - return resources.joinPath(iconThemeDocumentLocationDirname, path); + public load(data: FileIconThemeData): Promise { + if (!data.location) { + return Promise.resolve(data.styleSheetContent); + } + return this.loadIconThemeDocument(data.location).then(iconThemeDocument => { + const result = this.processIconThemeDocument(data.id, data.location!, iconThemeDocument); + data.styleSheetContent = result.content; + data.hasFileIcons = result.hasFileIcons; + data.hasFolderIcons = result.hasFolderIcons; + data.hidesExplorerArrows = result.hidesExplorerArrows; + data.isLoaded = true; + return data.styleSheetContent; + }); } - function collectSelectors(associations: IconsAssociation | undefined, baseThemeClassName?: string) { - function addSelector(selector: string, defId: string) { - if (defId) { - let list = selectorByDefinitionId[defId]; - if (!list) { - list = selectorByDefinitionId[defId] = []; - } - list.push(selector); - } - } - if (associations) { - let qualifier = '.show-file-icons'; - if (baseThemeClassName) { - qualifier = baseThemeClassName + ' ' + qualifier; + private loadIconThemeDocument(location: URI): Promise { + return this.fileService.readFile(location).then((content) => { + let errors: Json.ParseError[] = []; + let contentValue = Json.parse(content.value.toString(), errors); + if (errors.length > 0) { + return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); + } else if (Json.getNodeType(contentValue) !== 'object') { + return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for file icons theme file: Object expected."))); } + return Promise.resolve(contentValue); + }); + } - const expanded = '.monaco-tl-twistie.collapsible:not(.collapsed) + .monaco-tl-contents'; + private processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, iconThemeDocument: IconThemeDocument): { content: string; hasFileIcons: boolean; hasFolderIcons: boolean; hidesExplorerArrows: boolean; } { - if (associations.folder) { - addSelector(`${qualifier} .folder-icon::before`, associations.folder); - result.hasFolderIcons = true; - } + const result = { content: '', hasFileIcons: false, hasFolderIcons: false, hidesExplorerArrows: !!iconThemeDocument.hidesExplorerArrows }; - if (associations.folderExpanded) { - addSelector(`${qualifier} ${expanded} .folder-icon::before`, associations.folderExpanded); - result.hasFolderIcons = true; - } + if (!iconThemeDocument.iconDefinitions) { + return result; + } + let selectorByDefinitionId: { [def: string]: string[] } = {}; + let selectorByModeIdForMissingLanguages: { [languageId: string]: string[] } = {}; - let rootFolder = associations.rootFolder || associations.folder; - let rootFolderExpanded = associations.rootFolderExpanded || associations.folderExpanded; + const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation); + function resolvePath(path: string) { + return resources.joinPath(iconThemeDocumentLocationDirname, path); + } - if (rootFolder) { - addSelector(`${qualifier} .rootfolder-icon::before`, rootFolder); - result.hasFolderIcons = true; - } + const modeService = this.modeService; + const hasOwnProperty = Object.prototype.hasOwnProperty; - if (rootFolderExpanded) { - addSelector(`${qualifier} ${expanded} .rootfolder-icon::before`, rootFolderExpanded); - result.hasFolderIcons = true; + function collectSelectors(associations: IconsAssociation | undefined, baseThemeClassName?: string) { + function addSelector(selector: string, defId: string) { + if (defId) { + let list = selectorByDefinitionId[defId]; + if (!list) { + list = selectorByDefinitionId[defId] = []; + } + list.push(selector); + } } - if (associations.file) { - addSelector(`${qualifier} .file-icon::before`, associations.file); - result.hasFileIcons = true; + function collectSelectorsForMissingLanguages(qualifier: string, languageIds: { [languageId: string]: string }) { + for (let languageId of modeService.getRegisteredModes()) { + const iconName = modeService.getIconForMode(languageId); + if (iconName && !hasOwnProperty.call(languageIds, languageId)) { + let list = selectorByModeIdForMissingLanguages[languageId]; + if (!list) { + list = selectorByModeIdForMissingLanguages[languageId] = []; + } + list.push(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`); + } + } } - let folderNames = associations.folderNames; - if (folderNames) { - for (let folderName in folderNames) { - addSelector(`${qualifier} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNames[folderName]); + if (associations) { + let qualifier = '.show-file-icons'; + if (baseThemeClassName) { + qualifier = baseThemeClassName + ' ' + qualifier; + } + + const expanded = '.monaco-tl-twistie.collapsible:not(.collapsed) + .monaco-tl-contents'; + + if (associations.folder) { + addSelector(`${qualifier} .folder-icon::before`, associations.folder); result.hasFolderIcons = true; } - } - let folderNamesExpanded = associations.folderNamesExpanded; - if (folderNamesExpanded) { - for (let folderName in folderNamesExpanded) { - addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]); + + if (associations.folderExpanded) { + addSelector(`${qualifier} ${expanded} .folder-icon::before`, associations.folderExpanded); result.hasFolderIcons = true; } - } - let languageIds = associations.languageIds; - if (languageIds) { - if (!languageIds.jsonc && languageIds.json) { - languageIds.jsonc = languageIds.json; + let rootFolder = associations.rootFolder || associations.folder; + let rootFolderExpanded = associations.rootFolderExpanded || associations.folderExpanded; + + if (rootFolder) { + addSelector(`${qualifier} .rootfolder-icon::before`, rootFolder); + result.hasFolderIcons = true; } - for (let languageId in languageIds) { - addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]); + + if (rootFolderExpanded) { + addSelector(`${qualifier} ${expanded} .rootfolder-icon::before`, rootFolderExpanded); + result.hasFolderIcons = true; + } + + if (associations.file) { + addSelector(`${qualifier} .file-icon::before`, associations.file); result.hasFileIcons = true; } - } - let fileExtensions = associations.fileExtensions; - if (fileExtensions) { - for (let fileExtension in fileExtensions) { - let selectors: string[] = []; - let segments = fileExtension.toLowerCase().split('.'); - if (segments.length) { - for (let i = 0; i < segments.length; i++) { - selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); + + let folderNames = associations.folderNames; + if (folderNames) { + for (let folderName in folderNames) { + addSelector(`${qualifier} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNames[folderName]); + result.hasFolderIcons = true; + } + } + let folderNamesExpanded = associations.folderNamesExpanded; + if (folderNamesExpanded) { + for (let folderName in folderNamesExpanded) { + addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]); + result.hasFolderIcons = true; + } + } + + let languageIds = associations.languageIds; + if (languageIds) { + if (!languageIds.jsonc && languageIds.json) { + languageIds.jsonc = languageIds.json; + } + for (let languageId in languageIds) { + addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]); + result.hasFileIcons = true; + } + } + collectSelectorsForMissingLanguages(qualifier, languageIds || {}); + + let fileExtensions = associations.fileExtensions; + if (fileExtensions) { + for (let fileExtension in fileExtensions) { + let selectors: string[] = []; + let segments = fileExtension.toLowerCase().split('.'); + if (segments.length) { + for (let i = 0; i < segments.length; i++) { + selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); + } + selectors.push('.ext-file-icon'); // extra segment to increase file-ext score } - selectors.push('.ext-file-icon'); // extra segment to increase file-ext score + addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileExtensions[fileExtension]); + result.hasFileIcons = true; } - addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileExtensions[fileExtension]); - result.hasFileIcons = true; } - } - let fileNames = associations.fileNames; - if (fileNames) { - for (let fileName in fileNames) { - let selectors: string[] = []; - fileName = fileName.toLowerCase(); - selectors.push(`.${escapeCSS(fileName)}-name-file-icon`); - let segments = fileName.split('.'); - if (segments.length) { - for (let i = 1; i < segments.length; i++) { - selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); + let fileNames = associations.fileNames; + if (fileNames) { + for (let fileName in fileNames) { + let selectors: string[] = []; + fileName = fileName.toLowerCase(); + selectors.push(`.${escapeCSS(fileName)}-name-file-icon`); + let segments = fileName.split('.'); + if (segments.length) { + for (let i = 1; i < segments.length; i++) { + selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); + } + selectors.push('.ext-file-icon'); // extra segment to increase file-ext score } - selectors.push('.ext-file-icon'); // extra segment to increase file-ext score + addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[fileName]); + result.hasFileIcons = true; } - addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[fileName]); - result.hasFileIcons = true; } } } - } - collectSelectors(iconThemeDocument); - collectSelectors(iconThemeDocument.light, '.vs'); - collectSelectors(iconThemeDocument.highContrast, '.hc-black'); + collectSelectors(iconThemeDocument); + collectSelectors(iconThemeDocument.light, '.vs'); + collectSelectors(iconThemeDocument.highContrast, '.hc-black'); - if (!result.hasFileIcons && !result.hasFolderIcons) { - return result; - } + if (!result.hasFileIcons && !result.hasFolderIcons) { + return result; + } - let cssRules: string[] = []; + let cssRules: string[] = []; - let fonts = iconThemeDocument.fonts; - if (Array.isArray(fonts)) { - fonts.forEach(font => { - let src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', '); - cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; font-display: block; }`); - }); - cssRules.push(`.show-file-icons .file-icon::before, .show-file-icons .folder-icon::before, .show-file-icons .rootfolder-icon::before { font-family: '${fonts[0].id}'; font-size: ${fonts[0].size || '150%'}; }`); - } + let fonts = iconThemeDocument.fonts; + if (Array.isArray(fonts)) { + fonts.forEach(font => { + let src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', '); + cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; font-display: block; }`); + }); + cssRules.push(`.show-file-icons .file-icon::before, .show-file-icons .folder-icon::before, .show-file-icons .rootfolder-icon::before { font-family: '${fonts[0].id}'; font-size: ${fonts[0].size || '150%'}; }`); + } - for (let defId in selectorByDefinitionId) { - let selectors = selectorByDefinitionId[defId]; - let definition = iconThemeDocument.iconDefinitions[defId]; - if (definition) { - if (definition.iconPath) { - cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: ${asCSSUrl(resolvePath(definition.iconPath))}; }`); - } - if (definition.fontCharacter || definition.fontColor) { - let body = ''; - if (definition.fontColor) { - body += ` color: ${definition.fontColor};`; + for (let languageId in selectorByModeIdForMissingLanguages) { + const iconName = modeService.getIconForMode(languageId); + const contribution = getIconRegistry().getIcon(iconName!); + if (contribution) { + let definition: IconDefaults | null = contribution.defaults; + while (ThemeIcon.isThemeIcon(definition)) { + const c = getIconRegistry().getIcon(definition.id); + if (!c) { + definition = null; + break; + } + definition = c.defaults; } - if (definition.fontCharacter) { - body += ` content: '${definition.fontCharacter}';`; + if (!definition) { continue; } + let body = ''; + body += ` content: '${definition.fontCharacter}';`; + if (definition.fontId) { + body += ` font-family: ${asCSSPropertyValue(definition.fontId)};`; } - if (definition.fontSize) { - body += ` font-size: ${definition.fontSize};`; + cssRules.push(`${selectorByModeIdForMissingLanguages[languageId].join(', ')} { ${body} }`); + } + } + + for (let defId in selectorByDefinitionId) { + let selectors = selectorByDefinitionId[defId]; + let definition = iconThemeDocument.iconDefinitions[defId]; + if (definition) { + if (definition.iconPath) { + cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: ${asCSSUrl(resolvePath(definition.iconPath))}; }`); } - if (definition.fontId) { - body += ` font-family: ${definition.fontId};`; + if (definition.fontCharacter || definition.fontColor) { + let body = ''; + if (definition.fontColor) { + body += ` color: ${definition.fontColor};`; + } + if (definition.fontCharacter) { + body += ` content: '${definition.fontCharacter}';`; + } + if (definition.fontSize) { + body += ` font-size: ${definition.fontSize};`; + } + if (definition.fontId) { + body += ` font-family: ${definition.fontId};`; + } + else if (Array.isArray(fonts)) { + body += ` font-family: ${fonts[0].id};`; + } + cssRules.push(`${selectors.join(', ')} { ${body} }`); } - cssRules.push(`${selectors.join(', ')} { ${body} }`); } } + result.content = cssRules.join('\n'); + return result; } - result.content = cssRules.join('\n'); - return result; + } + function escapeCSS(str: string) { str = str.replace(/[\11\12\14\15\40]/g, '/'); // HTML class names can not contain certain whitespace characters, use / instead, which doesn't exist in file names. return window.CSS.escape(str); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index a04be9102b65f..b9c7b678579e2 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -39,6 +39,7 @@ import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hos import { RunOnceScheduler, Sequencer } from 'vs/base/common/async'; import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; // implementation @@ -113,7 +114,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService, @ILogService private readonly logService: ILogService, @IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService, - @IUserDataInitializationService readonly userDataInitializationService: IUserDataInitializationService + @IUserDataInitializationService readonly userDataInitializationService: IUserDataInitializationService, + @IInstantiationService readonly instantiationService: IInstantiationService ) { this.container = layoutService.container; this.settings = new ThemeConfiguration(configurationService); @@ -564,7 +566,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (iconTheme !== this.currentFileIconTheme.id || !this.currentFileIconTheme.isLoaded) { const newThemeData = this.fileIconThemeRegistry.findThemeById(iconTheme) || FileIconThemeData.noIconTheme; - await newThemeData.ensureLoaded(this.fileService); + await newThemeData.ensureLoaded(this.instantiationService); this.applyAndSetFileIconTheme(newThemeData); // updates this.currentFileIconTheme } @@ -583,7 +585,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private async reloadCurrentFileIconTheme() { return this.fileIconThemeSequencer.queue(async () => { - await this.currentFileIconTheme.reload(this.fileService); + await this.currentFileIconTheme.reload(this.instantiationService); this.applyAndSetFileIconTheme(this.currentFileIconTheme); }); } From 80a53695943a94d1e381d8d7163af04ee565c729 Mon Sep 17 00:00:00 2001 From: Mai-Lapyst Date: Sat, 18 Sep 2021 01:04:06 +0200 Subject: [PATCH 0003/2210] Using ThemeIcon in more places when refering to the icon for a language contribution --- src/vs/editor/common/services/languagesRegistry.ts | 7 ++++--- src/vs/editor/common/services/modeService.ts | 3 ++- src/vs/editor/common/services/modeServiceImpl.ts | 3 ++- .../workbench/services/themes/browser/fileIconThemeData.ts | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 194afe1fac821..1b39d594b48e5 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -15,6 +15,7 @@ import { NULL_LANGUAGE_IDENTIFIER, NULL_MODE_ID } from 'vs/editor/common/modes/n import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -26,7 +27,7 @@ export interface IResolvedLanguage { extensions: string[]; filenames: string[]; configurationFiles: URI[]; - icons: string[]; + icons: ThemeIcon[]; } export class LanguagesRegistry extends Disposable { @@ -231,7 +232,7 @@ export class LanguagesRegistry extends Disposable { } if (lang.icon) { - resolvedLanguage.icons.push(lang.icon); + resolvedLanguage.icons.push({ id: lang.icon }); } } @@ -281,7 +282,7 @@ export class LanguagesRegistry extends Disposable { return (language.mimetypes[0] || null); } - public getIconForMode(modeId: string): string | null { + public getIconForMode(modeId: string): ThemeIcon | null { if (!hasOwnProperty.call(this._languages, modeId)) { return null; } diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index 7b66fe0b7c1f5..df6759376378b 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -7,6 +7,7 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export const IModeService = createDecorator('modeService'); @@ -40,7 +41,7 @@ export interface IModeService { getExtensions(alias: string): string[]; getFilenames(alias: string): string[]; getMimeForMode(modeId: string): string | null; - getIconForMode(modeId: string): string | null; + getIconForMode(modeId: string): ThemeIcon | null; getLanguageName(modeId: string): string | null; getModeIdForLanguageName(alias: string): string | null; getModeIdByFilepathOrFirstLine(resource: URI, firstLine?: string): string | null; diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 38db18e569cd3..e61b81a383cd2 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -12,6 +12,7 @@ import { NULL_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/nullMode'; import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; import { firstOrDefault } from 'vs/base/common/arrays'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; class LanguageSelection implements ILanguageSelection { @@ -96,7 +97,7 @@ export class ModeServiceImpl extends Disposable implements IModeService { return this._registry.getMimeForMode(modeId); } - public getIconForMode(modeId: string): string | null { + public getIconForMode(modeId: string): ThemeIcon | null { return this._registry.getIconForMode(modeId); } diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index ff95f1d075841..51cbb89969b18 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -386,8 +386,8 @@ class FileIconThemeLoader { } for (let languageId in selectorByModeIdForMissingLanguages) { - const iconName = modeService.getIconForMode(languageId); - const contribution = getIconRegistry().getIcon(iconName!); + const themeIcon = modeService.getIconForMode(languageId); + const contribution = getIconRegistry().getIcon(themeIcon!.id!); if (contribution) { let definition: IconDefaults | null = contribution.defaults; while (ThemeIcon.isThemeIcon(definition)) { From 3243e421d01c82ac3d228b3ea39fc6d9fb70bb95 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 15 Oct 2021 20:44:47 +0200 Subject: [PATCH 0004/2210] change ILanguageExtensionPoint.icon to ThemeIcon, ignore for monaco --- src/vs/editor/common/services/languagesRegistry.ts | 2 +- src/vs/editor/common/services/modeService.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 1b39d594b48e5..53ad45b86ad67 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -232,7 +232,7 @@ export class LanguagesRegistry extends Disposable { } if (lang.icon) { - resolvedLanguage.icons.push({ id: lang.icon }); + resolvedLanguage.icons.push(lang.icon); } } diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index df6759376378b..cfc2c7f544f56 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -20,7 +20,10 @@ export interface ILanguageExtensionPoint { aliases?: string[]; mimetypes?: string[]; configuration?: URI; - icon?: string; + /** + * @internal + */ + icon?: ThemeIcon; } export interface ILanguageSelection { From 786535ad9e8cf400d9b2db19a57d1d40744399b6 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 15 Oct 2021 20:47:07 +0200 Subject: [PATCH 0005/2210] update monaco.d.ts --- src/vs/monaco.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index d96a11d85976d..69a604832abec 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6710,7 +6710,6 @@ declare namespace monaco.languages { aliases?: string[]; mimetypes?: string[]; configuration?: Uri; - icon?: string; } /** * A Monarch language definition From f5e059f871a14bc03a0afefb1d065bb0e22fdbf5 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 15 Oct 2021 23:15:55 +0200 Subject: [PATCH 0006/2210] extract IconContribution.getDefinition --- src/vs/platform/theme/browser/iconsStyleSheet.ts | 12 +++--------- src/vs/platform/theme/common/iconRegistry.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/theme/browser/iconsStyleSheet.ts b/src/vs/platform/theme/browser/iconsStyleSheet.ts index a255ac5c10ba8..939f81f602c51 100644 --- a/src/vs/platform/theme/browser/iconsStyleSheet.ts +++ b/src/vs/platform/theme/browser/iconsStyleSheet.ts @@ -6,8 +6,6 @@ import { asCSSPropertyValue, asCSSUrl } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { getIconRegistry, IconContribution, IconFontContribution } from 'vs/platform/theme/common/iconRegistry'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; - export interface IIconsStyleSheet { getCSS(): string; @@ -24,13 +22,9 @@ export function getIconsStyleSheet(): IIconsStyleSheet { getCSS() { const usedFontIds: { [id: string]: IconFontContribution } = {}; const formatIconRule = (contribution: IconContribution): string | undefined => { - let definition = contribution.defaults; - while (ThemeIcon.isThemeIcon(definition)) { - const c = iconRegistry.getIcon(definition.id); - if (!c) { - return undefined; - } - definition = c.defaults; + const definition = IconContribution.getDefinition(contribution, iconRegistry); + if (!definition) { + return undefined; } const fontId = definition.fontId; if (fontId) { diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index 505b300927c89..7c6ac9ccfa146 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -34,6 +34,20 @@ export interface IconContribution { defaults: IconDefaults; } +export namespace IconContribution { + export function getDefinition(contribution: IconContribution, registry: IIconRegistry): IconDefinition | undefined { + let definition = contribution.defaults; + while (ThemeIcon.isThemeIcon(definition)) { + const c = iconRegistry.getIcon(definition.id); + if (!c) { + return undefined; + } + definition = c.defaults; + } + return definition; + } +} + export interface IconFontContribution { id: string; definition: IconFontDefinition; From 78689aa8fe21752d936512dac20a5566434230f9 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 15 Oct 2021 23:16:28 +0200 Subject: [PATCH 0007/2210] Use ${iconName) notation --- .../workbench/services/mode/common/workbenchModeService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index a43bdbc819c6c..c7981ddb33342 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -16,6 +16,7 @@ import { FILES_ASSOCIATIONS_CONFIG, IFilesConfiguration } from 'vs/platform/file import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export interface IRawLanguageExtensionPoint { id: string; @@ -137,7 +138,7 @@ export class WorkbenchModeServiceImpl extends ModeServiceImpl { aliases: ext.aliases, mimetypes: ext.mimetypes, configuration: configuration, - icon: ext.icon + icon: ThemeIcon.fromString(ext.icon) }); } } @@ -235,8 +236,8 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec collector.error(nls.localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes')); return false; } - if (typeof value.icon !== 'undefined' && typeof value.icon !== 'string') { - collector.error(nls.localize('opt.icon', "property `{0}` can be omitted and must be of type `string`", 'icon')); + if (typeof value.icon !== 'undefined' && !ThemeIcon.fromString(value.icon)) { + collector.error(nls.localize('opt.icon', "property `{0}` can be omitted and must be of type `string`. It must in the form $([a-zA-Z0-9-]+)", 'icon')); return false; } return true; From 95266c710740cf4ca51adbdbae765728cb21f869 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 15 Oct 2021 23:19:08 +0200 Subject: [PATCH 0008/2210] Export FileIconThemeLoader and use in theme service --- .../themes/browser/fileIconThemeData.ts | 20 +++++++++---------- .../themes/browser/workbenchThemeService.ts | 12 ++++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 51cbb89969b18..75f3634c16dd2 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -13,7 +13,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { asCSSPropertyValue, asCSSUrl } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService } from 'vs/editor/common/services/modeService'; import { getIconRegistry, IconDefaults } from 'vs/platform/theme/common/iconRegistry'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -46,16 +45,16 @@ export class FileIconThemeData implements IWorkbenchFileIconTheme { this.hidesExplorerArrows = false; } - public ensureLoaded(instantiationService: IInstantiationService): Promise { - return !this.isLoaded ? this.load(instantiationService) : Promise.resolve(this.styleSheetContent); + public ensureLoaded(themeLoader: FileIconThemeLoader): Promise { + return !this.isLoaded ? this.load(themeLoader) : Promise.resolve(this.styleSheetContent); } - public reload(instantiationService: IInstantiationService): Promise { - return this.load(instantiationService); + public reload(themeLoader: FileIconThemeLoader): Promise { + return this.load(themeLoader); } - private load(instantiationService: IInstantiationService): Promise { - return instantiationService.createInstance(FileIconThemeLoader).load(this); + private load(themeLoader: FileIconThemeLoader): Promise { + return themeLoader.load(this); } static fromExtensionTheme(iconTheme: IThemeExtensionPoint, iconThemeLocation: URI, extensionData: ExtensionData): FileIconThemeData { @@ -190,13 +189,12 @@ interface IconThemeDocument extends IconsAssociation { hidesExplorerArrows?: boolean; } -class FileIconThemeLoader { +export class FileIconThemeLoader { constructor( - @IFileService private readonly fileService: IFileService, - @IModeService private readonly modeService: IModeService + private readonly fileService: IFileService, + private readonly modeService: IModeService ) { - } public load(data: FileIconThemeData): Promise { diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index b9c7b678579e2..813c83d7acd02 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -17,7 +17,7 @@ import { IColorTheme, Extensions as ThemingExtensions, IThemingRegistry } from ' import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData'; +import { FileIconThemeData, FileIconThemeLoader } from 'vs/workbench/services/themes/browser/fileIconThemeData'; import { createStyleSheet } from 'vs/base/browser/dom'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; @@ -39,7 +39,7 @@ import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hos import { RunOnceScheduler, Sequencer } from 'vs/base/common/async'; import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IModeService } from 'vs/editor/common/services/modeService'; // implementation @@ -92,6 +92,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private readonly fileIconThemeRegistry: ThemeRegistry; private currentFileIconTheme: FileIconThemeData; private readonly onFileIconThemeChange: Emitter; + private readonly fileIconThemeLoader: FileIconThemeLoader; private readonly fileIconThemeWatcher: ThemeFileWatcher; private readonly fileIconThemeSequencer: Sequencer; @@ -115,7 +116,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @ILogService private readonly logService: ILogService, @IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService, @IUserDataInitializationService readonly userDataInitializationService: IUserDataInitializationService, - @IInstantiationService readonly instantiationService: IInstantiationService + @IModeService readonly modeService: IModeService ) { this.container = layoutService.container; this.settings = new ThemeConfiguration(configurationService); @@ -128,6 +129,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.fileIconThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentFileIconTheme.bind(this)); this.fileIconThemeRegistry = new ThemeRegistry(fileIconThemesExtPoint, FileIconThemeData.fromExtensionTheme, true, FileIconThemeData.noIconTheme); + this.fileIconThemeLoader = new FileIconThemeLoader(fileService, modeService); this.onFileIconThemeChange = new Emitter(); this.currentFileIconTheme = FileIconThemeData.createUnloadedTheme(''); this.fileIconThemeSequencer = new Sequencer(); @@ -566,7 +568,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (iconTheme !== this.currentFileIconTheme.id || !this.currentFileIconTheme.isLoaded) { const newThemeData = this.fileIconThemeRegistry.findThemeById(iconTheme) || FileIconThemeData.noIconTheme; - await newThemeData.ensureLoaded(this.instantiationService); + await newThemeData.ensureLoaded(this.fileIconThemeLoader); this.applyAndSetFileIconTheme(newThemeData); // updates this.currentFileIconTheme } @@ -585,7 +587,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private async reloadCurrentFileIconTheme() { return this.fileIconThemeSequencer.queue(async () => { - await this.currentFileIconTheme.reload(this.instantiationService); + await this.currentFileIconTheme.reload(this.fileIconThemeLoader); this.applyAndSetFileIconTheme(this.currentFileIconTheme); }); } From 52955a8839a5423b6c9d8da2ab82d5b11f2e0231 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 15 Oct 2021 23:19:29 +0200 Subject: [PATCH 0009/2210] simplify css generation --- .../themes/browser/fileIconThemeData.ts | 71 +++++++------------ 1 file changed, 26 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 75f3634c16dd2..1909e22fdc5a0 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -14,8 +14,7 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { asCSSPropertyValue, asCSSUrl } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { getIconRegistry, IconDefaults } from 'vs/platform/theme/common/iconRegistry'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { getIconRegistry, IconContribution } from 'vs/platform/theme/common/iconRegistry'; export class FileIconThemeData implements IWorkbenchFileIconTheme { @@ -187,6 +186,7 @@ interface IconThemeDocument extends IconsAssociation { light?: IconsAssociation; highContrast?: IconsAssociation; hidesExplorerArrows?: boolean; + showLanguageModeIcons?: boolean; } export class FileIconThemeLoader { @@ -232,17 +232,14 @@ export class FileIconThemeLoader { if (!iconThemeDocument.iconDefinitions) { return result; } - let selectorByDefinitionId: { [def: string]: string[] } = {}; - let selectorByModeIdForMissingLanguages: { [languageId: string]: string[] } = {}; + const selectorByDefinitionId: { [def: string]: string[] } = {}; + const coveredLanguages: { [languageId: string]: boolean } = {}; const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation); function resolvePath(path: string) { return resources.joinPath(iconThemeDocumentLocationDirname, path); } - const modeService = this.modeService; - const hasOwnProperty = Object.prototype.hasOwnProperty; - function collectSelectors(associations: IconsAssociation | undefined, baseThemeClassName?: string) { function addSelector(selector: string, defId: string) { if (defId) { @@ -254,19 +251,6 @@ export class FileIconThemeLoader { } } - function collectSelectorsForMissingLanguages(qualifier: string, languageIds: { [languageId: string]: string }) { - for (let languageId of modeService.getRegisteredModes()) { - const iconName = modeService.getIconForMode(languageId); - if (iconName && !hasOwnProperty.call(languageIds, languageId)) { - let list = selectorByModeIdForMissingLanguages[languageId]; - if (!list) { - list = selectorByModeIdForMissingLanguages[languageId] = []; - } - list.push(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`); - } - } - } - if (associations) { let qualifier = '.show-file-icons'; if (baseThemeClassName) { @@ -326,10 +310,9 @@ export class FileIconThemeLoader { for (let languageId in languageIds) { addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]); result.hasFileIcons = true; + coveredLanguages[languageId] = true; } } - collectSelectorsForMissingLanguages(qualifier, languageIds || {}); - let fileExtensions = associations.fileExtensions; if (fileExtensions) { for (let fileExtension in fileExtensions) { @@ -383,29 +366,6 @@ export class FileIconThemeLoader { cssRules.push(`.show-file-icons .file-icon::before, .show-file-icons .folder-icon::before, .show-file-icons .rootfolder-icon::before { font-family: '${fonts[0].id}'; font-size: ${fonts[0].size || '150%'}; }`); } - for (let languageId in selectorByModeIdForMissingLanguages) { - const themeIcon = modeService.getIconForMode(languageId); - const contribution = getIconRegistry().getIcon(themeIcon!.id!); - if (contribution) { - let definition: IconDefaults | null = contribution.defaults; - while (ThemeIcon.isThemeIcon(definition)) { - const c = getIconRegistry().getIcon(definition.id); - if (!c) { - definition = null; - break; - } - definition = c.defaults; - } - if (!definition) { continue; } - let body = ''; - body += ` content: '${definition.fontCharacter}';`; - if (definition.fontId) { - body += ` font-family: ${asCSSPropertyValue(definition.fontId)};`; - } - cssRules.push(`${selectorByModeIdForMissingLanguages[languageId].join(', ')} { ${body} }`); - } - } - for (let defId in selectorByDefinitionId) { let selectors = selectorByDefinitionId[defId]; let definition = iconThemeDocument.iconDefinitions[defId]; @@ -434,6 +394,27 @@ export class FileIconThemeLoader { } } } + + if (iconThemeDocument.showLanguageModeIcons === true || (result.hasFileIcons && iconThemeDocument.showLanguageModeIcons !== false)) { + const iconRegistry = getIconRegistry(); + for (const languageId of this.modeService.getRegisteredModes()) { + if (!coveredLanguages[languageId]) { + const iconName = this.modeService.getIconForMode(languageId); + if (iconName) { + const iconContribution = iconRegistry.getIcon(iconName.id); + if (iconContribution) { + const definition = IconContribution.getDefinition(iconContribution, iconRegistry); + if (definition) { + const content = definition.fontCharacter; + const fontFamily = asCSSPropertyValue(definition.fontId ?? 'codicon'); + cssRules.push(`.show-file-icons .${escapeCSS(languageId)}-lang-file-icon.file-icon::before { content: '${content}'; font-family: ${fontFamily}; font-size: 16px; background-image: none };`); + } + } + } + } + } + } + result.content = cssRules.join('\n'); return result; } From b074018c3eb51f1ef5c159826ff3d43ffd1b2f0b Mon Sep 17 00:00:00 2001 From: Jan Kretschmer Date: Mon, 1 Nov 2021 14:50:32 +0100 Subject: [PATCH 0010/2210] sketch for virtual document support for --- .../client/src/customData.ts | 26 +++++++++++++++++-- .../client/src/htmlClient.ts | 4 ++- .../client/src/requests.ts | 20 +++++++++----- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index ecf964056e541..cc78f80954817 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -26,6 +26,13 @@ export function getCustomDataSource(toDispose: Disposable[]) { } })); + toDispose.push(workspace.onDidChangeTextDocument(e => { + const path = e.document.uri.toString(); + if (pathsInExtensions.indexOf(path) || pathsInWorkspace.indexOf(path)) { + onChange.fire(); + } + })); + return { get uris() { return pathsInWorkspace.concat(pathsInExtensions); @@ -50,7 +57,14 @@ function getCustomDataPathsInAllWorkspaces(): string[] { if (Array.isArray(paths)) { for (const path of paths) { if (typeof path === 'string') { - dataPaths.push(resolvePath(rootFolder, path).toString()); + const uri = Uri.parse(path); + if (uri.scheme === 'file') { + // only resolve file paths relative to extension + dataPaths.push(resolvePath(rootFolder, path).toString()); + } else { + // others schemes + dataPaths.push(path); + } } } } @@ -80,7 +94,15 @@ function getCustomDataPathsFromAllExtensions(): string[] { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { for (const rp of customData) { - dataPaths.push(joinPath(extension.extensionUri, rp).toString()); + const uri = Uri.parse(rp); + if (uri.scheme === 'file') { + // only resolve file paths relative to extension + dataPaths.push(joinPath(extension.extensionUri, rp).toString()); + } else { + // others schemes + dataPaths.push(rp); + } + } } } diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 42e75a297e5b9..3e7ca38be7ea9 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -16,7 +16,7 @@ import { DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, NotificationType, CommonLanguageClient } from 'vscode-languageclient'; import { activateTagClosing } from './tagClosing'; -import { RequestService } from './requests'; +import { RequestService, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; namespace CustomDataChangedNotification { @@ -120,6 +120,8 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua toDispose.push(disposable); client.onReady().then(() => { + serveFileSystemRequests(client, runtime, context.subscriptions); + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index f127c88562ff2..30c75595b7ea7 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -18,30 +18,36 @@ export namespace FsReadDirRequest { export const type: RequestType = new RequestType('fs/readDir'); } -export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime) { - client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { +export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime, subscriptions: { dispose(): any }[]) { + subscriptions.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { const uri = Uri.parse(param.uri); if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.getContent(param.uri); } + return workspace.fs.readFile(uri).then(buffer => { return new runtime.TextDecoder(param.encoding).decode(buffer); + }, () => { + // this path also considers TextDocumentContentProvider + return workspace.openTextDocument(uri).then(doc => { + return doc.getText(); + }); }); - }); - client.onRequest(FsReadDirRequest.type, (uriString: string) => { + })); + subscriptions.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.readDirectory(uriString); } return workspace.fs.readDirectory(uri); - }); - client.onRequest(FsStatRequest.type, (uriString: string) => { + })); + subscriptions.push(client.onRequest(FsStatRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.stat(uriString); } return workspace.fs.stat(uri); - }); + })); } export enum FileType { From e82ec6bee1118a00ec18ce3cf01886f68b07ee5a Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Tue, 2 Nov 2021 17:08:54 -0500 Subject: [PATCH 0011/2210] Install extension with version using workbench.extensions.installExtension --- .../contrib/extensions/browser/extensions.contribution.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 17f87d1147ef8..a57700ae7481d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -74,6 +74,7 @@ import { ExtensionsCompletionItemsProvider } from 'vs/workbench/contrib/extensio import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Event } from 'vs/base/common/event'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -289,7 +290,8 @@ CommandsRegistry.registerCommand({ const extensionGalleryService = accessor.get(IExtensionGalleryService); try { if (typeof arg === 'string') { - const [extension] = await extensionGalleryService.getExtensions([{ id: arg }], CancellationToken.None); + const [id, version] = getIdAndVersion(arg); + const [extension] = await extensionGalleryService.getExtensions([{ id, version }], CancellationToken.None); if (extension) { await extensionManagementService.installFromGallery(extension, options?.donotSync ? { isMachineScoped: true } : undefined); } else { From 6403829441b0e6eba0682e701a80c287e3b12c55 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Thu, 11 Nov 2021 10:43:47 -0500 Subject: [PATCH 0012/2210] disable auto update when installing a specific version of extension --- .../extensions/browser/extensions.contribution.ts | 9 +++++++-- .../extensions/browser/extensionsWorkbenchService.ts | 2 +- src/vs/workbench/contrib/extensions/common/extensions.ts | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index a57700ae7481d..18cc05e789bb2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -25,7 +25,7 @@ import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from ' import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -288,12 +288,17 @@ CommandsRegistry.registerCommand({ handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean, donotSync?: boolean }) => { const extensionManagementService = accessor.get(IExtensionManagementService); const extensionGalleryService = accessor.get(IExtensionGalleryService); + const extensionService = accessor.get(IExtensionsWorkbenchService); try { if (typeof arg === 'string') { const [id, version] = getIdAndVersion(arg); const [extension] = await extensionGalleryService.getExtensions([{ id, version }], CancellationToken.None); if (extension) { - await extensionManagementService.installFromGallery(extension, options?.donotSync ? { isMachineScoped: true } : undefined); + await extensionManagementService.installFromGallery(extension, { isMachineScoped: options?.donotSync, installGivenVersion: !!version }); + const localExt = extensionService.local.filter(e => areSameExtensions(e.identifier, { id: extension.identifier.id }))[0]; + if (version && localExt.latestVersion !== version) { + extensionService.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(extension.identifier, version)); + } } else { throw new Error(localize('notFound', "Extension '{0}' not found.", arg)); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 23ed11798840a..3030492222d4f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1388,7 +1388,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.storageService.store('extensions.ignoredAutoUpdateExtension', JSON.stringify(this._ignoredAutoUpdateExtensions), StorageScope.GLOBAL, StorageTarget.MACHINE); } - private ignoreAutoUpdate(identifierWithVersion: ExtensionIdentifierWithVersion): void { + ignoreAutoUpdate(identifierWithVersion: ExtensionIdentifierWithVersion): void { if (!this.isAutoUpdateIgnored(identifierWithVersion)) { this.ignoredAutoUpdateExtensions = [...this.ignoredAutoUpdateExtensions, identifierWithVersion.key()]; } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index fdbd7ae29ce98..9a91536e26d3b 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -10,7 +10,7 @@ import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier import { EnablementState, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable } from 'vs/base/common/lifecycle'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; @@ -99,6 +99,7 @@ export interface IExtensionsWorkbenchService { open(extension: IExtension, options?: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean, tab?: string }): Promise; checkForUpdates(): Promise; getExtensionStatus(extension: IExtension): IExtensionsStatus | undefined; + ignoreAutoUpdate(identifierWithVersion: ExtensionIdentifierWithVersion): void; // Sync APIs isExtensionIgnoredToSync(extension: IExtension): boolean; From 8779aaf2ae298cdd04565bf533eb7b3ee4008173 Mon Sep 17 00:00:00 2001 From: Jan Kretschmer Date: Sat, 20 Nov 2021 00:16:06 +0100 Subject: [PATCH 0013/2210] use set to store and lookup paths of interest --- .../client/src/customData.ts | 22 +++++++++---------- .../client/src/requests.ts | 19 ++++++++-------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index cc78f80954817..248ad18672c97 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -14,7 +14,7 @@ export function getCustomDataSource(toDispose: Disposable[]) { toDispose.push(extensions.onDidChange(_ => { const newPathsInExtensions = getCustomDataPathsFromAllExtensions(); - if (newPathsInExtensions.length !== pathsInExtensions.length || !newPathsInExtensions.every((val, idx) => val === pathsInExtensions[idx])) { + if (pathsInExtensions.size !== newPathsInExtensions.size || ![...pathsInExtensions].every(path => newPathsInExtensions.has(path))) { pathsInExtensions = newPathsInExtensions; onChange.fire(); } @@ -28,14 +28,14 @@ export function getCustomDataSource(toDispose: Disposable[]) { toDispose.push(workspace.onDidChangeTextDocument(e => { const path = e.document.uri.toString(); - if (pathsInExtensions.indexOf(path) || pathsInWorkspace.indexOf(path)) { + if (pathsInExtensions.has(path) || pathsInWorkspace.has(path)) { onChange.fire(); } })); return { get uris() { - return pathsInWorkspace.concat(pathsInExtensions); + return [...pathsInWorkspace].concat([...pathsInExtensions]); }, get onDidChange() { return onChange.event; @@ -44,10 +44,10 @@ export function getCustomDataSource(toDispose: Disposable[]) { } -function getCustomDataPathsInAllWorkspaces(): string[] { +function getCustomDataPathsInAllWorkspaces(): Set { const workspaceFolders = workspace.workspaceFolders; - const dataPaths: string[] = []; + const dataPaths = new Set(); if (!workspaceFolders) { return dataPaths; @@ -60,10 +60,10 @@ function getCustomDataPathsInAllWorkspaces(): string[] { const uri = Uri.parse(path); if (uri.scheme === 'file') { // only resolve file paths relative to extension - dataPaths.push(resolvePath(rootFolder, path).toString()); + dataPaths.add(resolvePath(rootFolder, path).toString()); } else { // others schemes - dataPaths.push(path); + dataPaths.add(path); } } } @@ -88,8 +88,8 @@ function getCustomDataPathsInAllWorkspaces(): string[] { return dataPaths; } -function getCustomDataPathsFromAllExtensions(): string[] { - const dataPaths: string[] = []; +function getCustomDataPathsFromAllExtensions(): Set { + const dataPaths = new Set(); for (const extension of extensions.all) { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { @@ -97,10 +97,10 @@ function getCustomDataPathsFromAllExtensions(): string[] { const uri = Uri.parse(rp); if (uri.scheme === 'file') { // only resolve file paths relative to extension - dataPaths.push(joinPath(extension.extensionUri, rp).toString()); + dataPaths.add(joinPath(extension.extensionUri, rp).toString()); } else { // others schemes - dataPaths.push(rp); + dataPaths.add(rp); } } diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index 30c75595b7ea7..e5bc857dabd1a 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -21,18 +21,19 @@ export namespace FsReadDirRequest { export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime, subscriptions: { dispose(): any }[]) { subscriptions.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { const uri = Uri.parse(param.uri); - if (uri.scheme === 'file' && runtime.fs) { - return runtime.fs.getContent(param.uri); - } - - return workspace.fs.readFile(uri).then(buffer => { - return new runtime.TextDecoder(param.encoding).decode(buffer); - }, () => { - // this path also considers TextDocumentContentProvider + if (uri.scheme === 'file') { + if (runtime.fs) { + return runtime.fs.getContent(param.uri); + } else { + return workspace.fs.readFile(uri).then(buffer => { + return new runtime.TextDecoder(param.encoding).decode(buffer); + }); + } + } else { return workspace.openTextDocument(uri).then(doc => { return doc.getText(); }); - }); + } })); subscriptions.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); From 0d23031b6dfb0245cbf7adc87527b580f0a1a14f Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Sun, 21 Nov 2021 23:10:19 +0100 Subject: [PATCH 0014/2210] Add singular form of bisect message --- .../services/extensionManagement/browser/extensionBisect.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts b/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts index ae29531fcb914..6561c81242c06 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts @@ -195,9 +195,13 @@ class ExtensionBisectUi { run: () => this._commandService.executeCommand('extension.bisect.stop') }; + const message = this._extensionBisectService.disabledCount === 1 + ? localize('bisect.singular', "Extension Bisect is active and has disabled 1 extension. Check if you can still reproduce the problem and proceed by selecting from these options.") + : localize('bisect.plural', "Extension Bisect is active and has disabled {0} extensions. Check if you can still reproduce the problem and proceed by selecting from these options.", this._extensionBisectService.disabledCount); + this._notificationService.prompt( Severity.Info, - localize('bisect', "Extension Bisect is active and has disabled {0} extensions. Check if you can still reproduce the problem and proceed by selecting from these options.", this._extensionBisectService.disabledCount), + message, [goodPrompt, badPrompt, stop], { sticky: true } ); From 597a83411574cf4f6e6781c9321e0ac3352f55d8 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 22 Nov 2021 22:47:11 +0300 Subject: [PATCH 0015/2210] make typings on `showInformationMessage`, `showWarningMessage`, `showErrorMessage` strict with items as strings --- src/vscode-dts/vscode.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index ff186b0103ca3..4872c4c2603e1 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -8786,7 +8786,7 @@ declare module 'vscode' { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - export function showInformationMessage(message: string, ...items: string[]): Thenable; + export function showInformationMessage(message: string, ...items: T[]): Thenable; /** * Show an information message to users. Optionally provide an array of items which will be presented as @@ -8797,7 +8797,7 @@ declare module 'vscode' { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - export function showInformationMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; + export function showInformationMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; /** * Show an information message. @@ -8831,7 +8831,7 @@ declare module 'vscode' { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - export function showWarningMessage(message: string, ...items: string[]): Thenable; + export function showWarningMessage(message: string, ...items: T[]): Thenable; /** * Show a warning message. @@ -8843,7 +8843,7 @@ declare module 'vscode' { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - export function showWarningMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; + export function showWarningMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; /** * Show a warning message. @@ -8877,7 +8877,7 @@ declare module 'vscode' { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - export function showErrorMessage(message: string, ...items: string[]): Thenable; + export function showErrorMessage(message: string, ...items: T[]): Thenable; /** * Show an error message. @@ -8889,7 +8889,7 @@ declare module 'vscode' { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - export function showErrorMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; + export function showErrorMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; /** * Show an error message. From 602c83b7bcb66b6ed25577f8b144fe9be3824361 Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Wed, 24 Nov 2021 11:09:36 -0500 Subject: [PATCH 0016/2210] Fix a few typos in doc comments --- src/vs/editor/common/model.ts | 4 ++-- src/vs/editor/common/modes.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 33b0669d37e5e..3608d9046d2d8 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -760,7 +760,7 @@ export interface ITextModel { getLineLastNonWhitespaceColumn(lineNumber: number): number; /** - * Create a valid position, + * Create a valid position. */ validatePosition(position: IPosition): Position; @@ -800,7 +800,7 @@ export interface ITextModel { getPositionAt(offset: number): Position; /** - * Get a range covering the entire model + * Get a range covering the entire model. */ getFullModelRange(): Range; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 0d8e94fe9fabb..ceaff9385dd1f 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -539,11 +539,10 @@ export interface CompletionItem { /** * A string or snippet that should be inserted in a document when selecting * this completion. - * is used. */ insertText: string; /** - * Addition rules (as bitmask) that should be applied when inserting + * Additional rules (as bitmask) that should be applied when inserting * this completion. */ insertTextRules?: CompletionItemInsertTextRule; From 524538eeccfcbcfd84fe083f437448d9d34793e6 Mon Sep 17 00:00:00 2001 From: Justin Grote Date: Wed, 24 Nov 2021 12:05:26 -0800 Subject: [PATCH 0017/2210] Correction booleanRegex to properly match only 'true' and 'false' Closes #137818 --- src/vs/workbench/contrib/debug/browser/baseDebugView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index b8b5f67b4bf65..295056cc357c9 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -22,7 +22,7 @@ import { once } from 'vs/base/common/functional'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; -const booleanRegex = /^true|false$/i; +const booleanRegex = /^(true|false)$/i; const stringRegex = /^(['"]).*\1$/; const $ = dom.$; From e346d0f1f9b3b94302a06805ae6bbb18d95f1e6f Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Wed, 24 Nov 2021 21:44:29 +0100 Subject: [PATCH 0018/2210] Add script to prefetch headers for native module compile --- build/prefetchHeaders.js | 37 +++++++++++++++++++++++++++++++++++++ package.json | 3 ++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 build/prefetchHeaders.js diff --git a/build/prefetchHeaders.js b/build/prefetchHeaders.js new file mode 100644 index 0000000000000..030f4aaf1d036 --- /dev/null +++ b/build/prefetchHeaders.js @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const path = require('path'); +const fs = require('fs'); +const cp = require('child_process'); + +function installHeaders(rcFile) { + const lines = fs.readFileSync(rcFile, 'utf8').split(/\r\n?/g); + let disturl, target; + for (const line of lines) { + let match = line.match(/\s*disturl\s*(.*)$/); + if (match !== null && match.length >= 1) { + disturl = match[1]; + } + match = line.match(/\s*target\s*(.*)$/); + if (match !== null && match.length >= 1) { + target = match[1]; + } + } + if (disturl !== undefined && target !== undefined) { + console.log(`Pre-fetch headers for ${target} from ${disturl}`); + cp.execSync(`node-gyp install --dist-url ${disturl} ${target}`); + } +} + + +function main() { + installHeaders(path.join(__dirname, '..', '.yarnrc')); + installHeaders(path.join(__dirname, '..', 'remote', '.yarnrc')); +} + + +if (require.main === module) { + main(); +} diff --git a/package.json b/package.json index 129bab504cdc1..4ce4e25985f90 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "minify-vscode-reh-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web", "hygiene": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js hygiene", "core-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js core-ci", - "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci" + "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci", + "refetch-headers": "node build/prefetchHeaders.js" }, "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", From 008a52e298de8d3502374f298b432e7412bc8a13 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Wed, 24 Nov 2021 21:46:36 +0100 Subject: [PATCH 0019/2210] Fix typo --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ce4e25985f90..1f34faf5c2f7c 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "hygiene": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js hygiene", "core-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js core-ci", "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci", - "refetch-headers": "node build/prefetchHeaders.js" + "prefetch-headers": "node build/prefetchHeaders.js" }, "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", From bb89815cfb253af45a9cab30b9da7b89efa67ff1 Mon Sep 17 00:00:00 2001 From: Jan Kretschmer Date: Wed, 24 Nov 2021 22:07:31 +0100 Subject: [PATCH 0020/2210] use regex, not Uri.parse, to detect custom scheme --- .../client/src/customData.ts | 13 +++++------ .../client/src/requests.ts | 22 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index 248ad18672c97..146fef2f3ee64 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode'; -import { resolvePath, joinPath } from './requests'; +import { resolvePath, joinPath, uriScheme } from './requests'; + export function getCustomDataSource(toDispose: Disposable[]) { let pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); @@ -57,8 +58,7 @@ function getCustomDataPathsInAllWorkspaces(): Set { if (Array.isArray(paths)) { for (const path of paths) { if (typeof path === 'string') { - const uri = Uri.parse(path); - if (uri.scheme === 'file') { + if (!uriScheme.test(path)) { // only resolve file paths relative to extension dataPaths.add(resolvePath(rootFolder, path).toString()); } else { @@ -94,12 +94,11 @@ function getCustomDataPathsFromAllExtensions(): Set { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { for (const rp of customData) { - const uri = Uri.parse(rp); - if (uri.scheme === 'file') { - // only resolve file paths relative to extension + if (!uriScheme.test(rp)) { + // no schame -> resolve relative to extension dataPaths.add(joinPath(extension.extensionUri, rp).toString()); } else { - // others schemes + // actual schemes dataPaths.add(rp); } diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index e5bc857dabd1a..aa75e6b615f2c 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -7,6 +7,8 @@ import { Uri, workspace } from 'vscode'; import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; import { Runtime } from './htmlClient'; +export const uriScheme = /^(?\w[\w\d+.-]*):/; + export namespace FsContentRequest { export const type: RequestType<{ uri: string; encoding?: string; }, string, any> = new RequestType('fs/content'); } @@ -20,34 +22,34 @@ export namespace FsReadDirRequest { export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime, subscriptions: { dispose(): any }[]) { subscriptions.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { - const uri = Uri.parse(param.uri); - if (uri.scheme === 'file') { + const uri = param.uri.match(uriScheme); + if (uri?.groups?.scheme === 'file') { if (runtime.fs) { return runtime.fs.getContent(param.uri); } else { - return workspace.fs.readFile(uri).then(buffer => { + return workspace.fs.readFile(Uri.parse(param.uri)).then(buffer => { return new runtime.TextDecoder(param.encoding).decode(buffer); }); } } else { - return workspace.openTextDocument(uri).then(doc => { + return workspace.openTextDocument(Uri.parse(param.uri)).then(doc => { return doc.getText(); }); } })); subscriptions.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { - const uri = Uri.parse(uriString); - if (uri.scheme === 'file' && runtime.fs) { + const uri = uriString.match(uriScheme); + if (uri?.groups?.scheme === 'file' && runtime.fs) { return runtime.fs.readDirectory(uriString); } - return workspace.fs.readDirectory(uri); + return workspace.fs.readDirectory(Uri.parse(uriString)); })); subscriptions.push(client.onRequest(FsStatRequest.type, (uriString: string) => { - const uri = Uri.parse(uriString); - if (uri.scheme === 'file' && runtime.fs) { + const uri = uriString.match(uriScheme); + if (uri?.groups?.scheme === 'file' && runtime.fs) { return runtime.fs.stat(uriString); } - return workspace.fs.stat(uri); + return workspace.fs.stat(Uri.parse(uriString)); })); } From 4a5d3623c0e9a584f757a7dbbcf98227da99c045 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Wed, 24 Nov 2021 13:08:49 -0800 Subject: [PATCH 0021/2210] Polish --- src/vs/workbench/contrib/preferences/browser/settingsTree.ts | 2 +- .../contrib/preferences/browser/settingsTreeModels.ts | 4 ++-- .../configuration/common/configurationEditingService.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 475c52528f2c5..d15f9878eab88 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -971,7 +971,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre if (content.startsWith('#')) { const e: ISettingLinkClickEvent = { source: element, - targetKey: content.substr(1) + targetKey: content.substring(1) }; this._onDidClickSettingLink.fire(e); } else { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index a2dd74da7754a..27b1cbbc00b39 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -507,8 +507,8 @@ export function settingKeyToDisplayFormat(key: string, groupId = ''): { category const lastDotIdx = key.lastIndexOf('.'); let category = ''; if (lastDotIdx >= 0) { - category = key.substr(0, lastDotIdx); - key = key.substr(lastDotIdx + 1); + category = key.substring(0, lastDotIdx); + key = key.substring(lastDotIdx + 1); } groupId = groupId.replace(/\//g, '.'); diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index c18ee164b6462..47106e1e7489a 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -199,7 +199,7 @@ export class ConfigurationEditingService { } } - private async updateConfiguration(operation: IConfigurationEditOperation, model: ITextModel, formattingOptions: FormattingOptions): Promise { + private async updateConfiguration(operation: IConfigurationEditOperation, model: ITextModel, formattingOptions: FormattingOptions): Promise { if (this.hasParseErrors(model.getValue(), operation)) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, operation.target, operation); } From 90ca212f5fff320414f883826eba13ceb5d5113c Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 24 Nov 2021 12:09:18 -0800 Subject: [PATCH 0022/2210] Clear Notebook Editor Cache --- .../browser/contrib/troubleshoot/layout.ts | 17 +++++++++++++++++ .../notebook/browser/notebookServiceImpl.ts | 10 ++++++++++ .../contrib/notebook/common/notebookService.ts | 1 + 3 files changed, 28 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index 23aded1e61431..dd9356390343f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -10,6 +10,7 @@ import { CATEGORIES } from 'vs/workbench/common/actions'; import { getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class TroubleshootController extends Disposable implements INotebookEditorContribution { @@ -129,3 +130,19 @@ registerAction2(class extends Action2 { } } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.clearNotebookEdtitorTypeCache', + title: 'Clear Notebook Editor Cache', + category: CATEGORIES.Developer, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const notebookService = accessor.get(INotebookService); + notebookService.clearEditorCache(); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index ff39f09416d96..735e9201e8ef2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -135,6 +135,12 @@ export class NotebookProviderInfoStore extends Disposable { this._memento.saveMemento(); } + clearEditorCache() { + const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); + mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = []; + this._memento.saveMemento(); + } + private _convertPriority(priority?: string) { if (!priority) { return RegisteredEditorPriority.default; @@ -523,6 +529,10 @@ export class NotebookService extends Disposable implements INotebookService { })); } + clearEditorCache(): void { + this.notebookProviderInfoStore.clearEditorCache(); + } + private _postDocumentOpenActivation(viewType: string) { // send out activations on notebook text model creation this._extensionService.activateByEvent(`onNotebook:${viewType}`); diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 1d810d2d6dea0..32132d064389a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -92,4 +92,5 @@ export interface INotebookService { setToCopy(items: NotebookCellTextModel[], isCopy: boolean): void; getToCopy(): { items: NotebookCellTextModel[], isCopy: boolean; } | undefined; + clearEditorCache(): void; } From 1f581e229d7ad1c3bf44e1c3cd85d1c180c82129 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 24 Nov 2021 14:30:55 -0800 Subject: [PATCH 0023/2210] Update doc --- .../workbench/contrib/notebook/browser/notebookEditorWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 5bcbb759b2300..05bc82e77054b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2739,7 +2739,7 @@ export const focusedCellBorder = registerColor('notebook.focusedCellBorder', { dark: focusBorder, light: focusBorder, hc: focusBorder -}, nls.localize('notebook.focusedCellBorder', "The color of the cell's borders when the cell is focused.")); +}, nls.localize('notebook.focusedCellBorder', "The color of the cell's focus indicator borders when the cell is focused.")); export const inactiveFocusedCellBorder = registerColor('notebook.inactiveFocusedCellBorder', { dark: notebookCellBorder, From 19a83b8c4799f76a6342fe7291a2a62f456637fc Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 24 Nov 2021 23:42:07 +0100 Subject: [PATCH 0024/2210] color theme picker: separate quick pick for browsing --- .../themes/browser/themes.contribution.ts | 173 ++++++++++++------ .../themes/browser/workbenchThemeService.ts | 76 ++++---- 2 files changed, 156 insertions(+), 93 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 3c45fdbe57276..d4ba184c2574f 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -40,6 +40,7 @@ export class SelectColorThemeAction extends Action { static readonly LABEL = localize('selectTheme.label', "Color Theme"); static readonly INSTALL_ADDITIONAL = localize('installColorThemes', "Install Additional Color Themes..."); + static readonly BROWSE_ADDITIONAL = '$(plus) ' + localize('browseColorThemes', "Browse Additional Color Themes..."); constructor( @@ -61,11 +62,15 @@ export class SelectColorThemeAction extends Action { return this.themeService.getColorThemes().then(themes => { const currentTheme = this.themeService.getColorTheme(); + const supportsGallery = this.extensionGalleryService.isEnabled(); + const supportsGalleryPreview = supportsGallery && this.extensionResourceLoaderService.supportsExtensionGalleryResources; + const picks: QuickPickInput[] = [ + ...(supportsGalleryPreview ? configurationEntries(SelectColorThemeAction.BROWSE_ADDITIONAL) : []), ...toEntries(themes.filter(t => t.type === ColorScheme.LIGHT), localize('themes.category.light', "light themes")), ...toEntries(themes.filter(t => t.type === ColorScheme.DARK), localize('themes.category.dark', "dark themes")), ...toEntries(themes.filter(t => t.type === ColorScheme.HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")), - ...configurationEntries(SelectColorThemeAction.INSTALL_ADDITIONAL) + ...(supportsGallery && !supportsGalleryPreview ? configurationEntries(SelectColorThemeAction.INSTALL_ADDITIONAL) : []), ]; let selectThemeTimeout: number | undefined; @@ -87,80 +92,128 @@ export class SelectColorThemeAction extends Action { }; return new Promise((s, _) => { - let isCompleted = false; - - const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === currentTheme.id); - const quickpick = this.quickInputService.createQuickPick(); - quickpick.items = picks; - quickpick.sortByLabel = false; - quickpick.matchOnDescription = true; - quickpick.placeholder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); - quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; - quickpick.canSelectMany = false; - quickpick.onDidAccept(async _ => { - const themeItem = quickpick.activeItems[0]; - if (!themeItem || themeItem.theme === undefined) { // 'pick in marketplace' entry - openExtensionViewlet(this.paneCompositeService, `category:themes ${quickpick.value}`); - } else { - let themeToSet = themeItem.theme; - if (themeItem.galleryExtension) { - const success = await this.installExtension(themeItem.galleryExtension); - if (!success) { - themeToSet = currentTheme; + const browseInstalledThemes = (activeItemId: string | undefined) => { + let isCompleted = false; + + const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === activeItemId); + const quickpick = this.quickInputService.createQuickPick(); + quickpick.items = picks; + quickpick.sortByLabel = false; + quickpick.matchOnDescription = true; + quickpick.placeholder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); + quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; + quickpick.canSelectMany = false; + quickpick.onDidAccept(async _ => { + const themeItem = quickpick.activeItems[0]; + if (!themeItem || themeItem.theme === undefined) { // 'pick in marketplace' entry + if (supportsGalleryPreview) { + browseMarketplaceThemes(quickpick.value); + } else { + openExtensionViewlet(this.paneCompositeService, `category:themes ${quickpick.value}`); } - } - selectTheme(themeToSet, true); - } - isCompleted = true; - quickpick.hide(); - s(); - }); - quickpick.onDidTriggerItemButton(e => { - if (isItem(e.item)) { - const extensionId = e.item.theme?.extensionData?.extensionId; - if (extensionId) { - openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`); } else { - openExtensionViewlet(this.paneCompositeService, `category:themes ${quickpick.value}`); + let themeToSet = themeItem.theme; + selectTheme(themeToSet, true); } - } - }); - - quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false)); - quickpick.onDidHide(() => { - if (!isCompleted) { - selectTheme(currentTheme, true); - s(); isCompleted = true; - } - }); - quickpick.show(); + quickpick.hide(); + s(); + }); + quickpick.onDidTriggerItemButton(e => { + if (isItem(e.item)) { + const extensionId = e.item.theme?.extensionData?.extensionId; + if (extensionId) { + openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`); + } else { + openExtensionViewlet(this.paneCompositeService, `category:themes ${quickpick.value}`); + } + } + }); - if (this.extensionGalleryService.isEnabled() && this.extensionResourceLoaderService.supportsExtensionGalleryResources) { + quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false)); + quickpick.onDidHide(() => { + if (!isCompleted) { + selectTheme(currentTheme, true); + s(); + isCompleted = true; + } + }); + quickpick.show(); + }; + const browseMarketplaceThemes = (value: string) => { + let isCompleted = false; const marketplaceThemes = new MarketplaceThemes(this.extensionGalleryService, this.extensionManagementService, this.themeService, this.logService); + + const mp_quickpick = this.quickInputService.createQuickPick(); + mp_quickpick.items = []; + mp_quickpick.sortByLabel = false; + mp_quickpick.matchOnDescription = true; + mp_quickpick.buttons = [this.quickInputService.backButton] + mp_quickpick.title = 'Marketplace Themes'; + mp_quickpick.placeholder = localize('themes.selectTheme', "Type to Search More. Select to Install. Up/Down Keys to Preview"); + mp_quickpick.canSelectMany = false; + mp_quickpick.onDidChangeValue(() => marketplaceThemes.trigger(mp_quickpick.value)); + mp_quickpick.onDidAccept(async _ => { + let themeItem = mp_quickpick.activeItems[0]; + if (themeItem?.galleryExtension) { + isCompleted = true; + mp_quickpick.hide(); + const success = await this.installExtension(themeItem.galleryExtension); + if (success) { + selectTheme(themeItem.theme, true); + } + s(); + } + }); + + mp_quickpick.onDidTriggerItemButton(e => { + if (isItem(e.item)) { + const extensionId = e.item.theme?.extensionData?.extensionId; + if (extensionId) { + openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`); + } else { + openExtensionViewlet(this.paneCompositeService, `category:themes ${mp_quickpick.value}`); + } + } + }); + mp_quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false)); + + mp_quickpick.onDidHide(() => { + if (!isCompleted) { + selectTheme(currentTheme, true); + isCompleted = true; + } + marketplaceThemes.dispose(); + }); + + mp_quickpick.onDidTriggerButton(e => { + if (e === this.quickInputService.backButton) { + mp_quickpick.hide(); + browseInstalledThemes(undefined); + } + }); + marketplaceThemes.onDidChange(() => { - const items = picks.concat(...marketplaceThemes.themes); + let items = marketplaceThemes.themes; if (marketplaceThemes.isSearching) { - items.push({ label: '$(sync~spin) Searching for themes...', id: undefined, alwaysShow: true }); + items = items.concat({ label: '$(sync~spin) Searching for themes...', id: undefined, alwaysShow: true }); } - const activeItemId = quickpick.activeItems[0]?.id; + const activeItemId = mp_quickpick.activeItems[0]?.id; const newActiveItem = activeItemId ? items.find(i => isItem(i) && i.id === activeItemId) : undefined; - quickpick.items = items; + mp_quickpick.items = items; if (newActiveItem) { - quickpick.activeItems = [newActiveItem as ThemeItem]; + mp_quickpick.activeItems = [newActiveItem as ThemeItem]; } }); - - quickpick.onDidChangeValue(() => marketplaceThemes.trigger(quickpick.value)); - marketplaceThemes.trigger(quickpick.value); - - quickpick.onDidHide(() => { - marketplaceThemes.dispose(); - }); + marketplaceThemes.trigger(value); + mp_quickpick.show(); } + + browseInstalledThemes(currentTheme.id); }); + }); } @@ -170,8 +223,8 @@ export class SelectColorThemeAction extends Action { await this.progressService.withProgress({ location: ProgressLocation.Notification, title: localize('installing extensions', "Installing Extension {0}...", galleryExtension.displayName) - }, () => { - return this.extensionManagementService.installFromGallery(galleryExtension); + }, async () => { + await this.extensionManagementService.installFromGallery(galleryExtension); }); return true; } catch (e) { diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index aa5970e8d299e..169e31eacc47d 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -291,16 +291,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (await this.restoreColorTheme()) { // checks if theme from settings exists and is set // restore theme if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) { - // restore theme - this.setColorTheme(prevColorId, 'auto'); + await this.setColorTheme(prevColorId, 'auto'); prevColorId = undefined; } else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) { - this.reloadCurrentColorTheme(); + await this.reloadCurrentColorTheme(); } } else if (event.removed.some(t => t.settingsId === this.currentColorTheme.settingsId)) { // current theme is no longer available prevColorId = this.currentColorTheme.id; - this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto'); + await this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto'); } }); @@ -310,15 +309,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (await this.restoreFileIconTheme()) { // checks if theme from settings exists and is set // restore theme if (this.currentFileIconTheme.id === DEFAULT_FILE_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && this.fileIconThemeRegistry.findThemeById(prevFileIconId)) { - this.setFileIconTheme(prevFileIconId, 'auto'); + await this.setFileIconTheme(prevFileIconId, 'auto'); prevFileIconId = undefined; } else if (event.added.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) { - this.reloadCurrentFileIconTheme(); + await this.reloadCurrentFileIconTheme(); } } else if (event.removed.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) { // current theme is no longer available prevFileIconId = this.currentFileIconTheme.id; - this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto'); + await this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto'); } }); @@ -329,15 +328,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (await this.restoreProductIconTheme()) { // checks if theme from settings exists and is set // restore theme if (this.currentProductIconTheme.id === DEFAULT_PRODUCT_ICON_THEME_ID && !types.isUndefined(prevProductIconId) && this.productIconThemeRegistry.findThemeById(prevProductIconId)) { - this.setProductIconTheme(prevProductIconId, 'auto'); + await this.setProductIconTheme(prevProductIconId, 'auto'); prevProductIconId = undefined; } else if (event.added.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) { - this.reloadCurrentProductIconTheme(); + await this.reloadCurrentProductIconTheme(); } } else if (event.removed.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) { // current theme is no longer available prevProductIconId = this.currentProductIconTheme.id; - this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto'); + await this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto'); } }); @@ -410,7 +409,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public async getMarketplaceColorThemes(id: string, version: string): Promise { const [publisher, name] = id.split('.'); - const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version}, 'extension'); + const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version }, 'extension'); if (!extensionLocation) { return []; } @@ -432,36 +431,42 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } public setColorTheme(themeIdOrTheme: string | undefined | IWorkbenchColorTheme, settingsTarget: ThemeSettingTarget): Promise { - return this.colorThemeSequencer.queue(() => { + return this.colorThemeSequencer.queue(async () => { if (!themeIdOrTheme) { - return Promise.resolve(null); + return null; } - if (this.currentColorTheme.isLoaded && (themeIdOrTheme === this.currentColorTheme || themeIdOrTheme === this.currentColorTheme.id)) { + const themeId = types.isString(themeIdOrTheme) ? validateThemeId(themeIdOrTheme) : themeIdOrTheme.id; + if (this.currentColorTheme.isLoaded && themeId === this.currentColorTheme.id) { if (settingsTarget !== 'preview') { this.currentColorTheme.toStorage(this.storageService); } return this.settings.setColorTheme(this.currentColorTheme, settingsTarget); } - const themeData = types.isString(themeIdOrTheme) ? this.colorThemeRegistry.findThemeById(validateThemeId(themeIdOrTheme), DEFAULT_COLOR_THEME_ID) : themeIdOrTheme; - if (!(themeData instanceof ColorThemeData)) { - return Promise.resolve(null); + let themeData = this.colorThemeRegistry.findThemeById(themeId); + if (!themeData) { + if (themeIdOrTheme instanceof ColorThemeData) { + themeData = themeIdOrTheme; + } else { + return null; + } } - return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => { - themeData.setCustomizations(this.settings); + try { + await themeData.ensureLoaded(this.extensionResourceLoaderService); return this.applyTheme(themeData, settingsTarget); - }, error => { - return Promise.reject(new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location?.toString(), error.message))); - }); + } catch (error) { + throw new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location?.toString(), error.message)); + } }); } private reloadCurrentColorTheme() { return this.colorThemeSequencer.queue(async () => { try { - await this.currentColorTheme.reload(this.extensionResourceLoaderService); - this.currentColorTheme.setCustomizations(this.settings); - await this.applyTheme(this.currentColorTheme, undefined, false); + const theme = this.colorThemeRegistry.findThemeBySettingsId(this.currentColorTheme.settingsId) || this.currentColorTheme; + await theme.reload(this.extensionResourceLoaderService); + theme.setCustomizations(this.settings); + await this.applyTheme(theme, undefined, false); } catch (error) { this.logService.info('Unable to reload {0}: {1}', this.currentColorTheme.location?.toString()); } @@ -469,15 +474,20 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } public async restoreColorTheme(): Promise { - const settingId = this.settings.colorTheme; - const theme = this.colorThemeRegistry.findThemeBySettingsId(settingId); - if (theme) { - if (settingId !== this.currentColorTheme.settingsId) { - await this.setColorTheme(theme.id, undefined); + return this.colorThemeSequencer.queue(async () => { + const settingId = this.settings.colorTheme; + const theme = this.colorThemeRegistry.findThemeBySettingsId(settingId); + if (theme) { + if (settingId !== this.currentColorTheme.settingsId) { + await this.setColorTheme(theme.id, undefined); + } else if (theme !== this.currentColorTheme) { + theme.setCustomizations(this.settings); + await this.applyTheme(theme, undefined, true); + } + return true; } - return true; - } - return false; + return false; + }); } private updateDynamicCSSRules(themeData: IColorTheme) { From f26a89c2c3d6cf2abab327bcd4bbd620e20ec848 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 24 Nov 2021 23:20:46 +0100 Subject: [PATCH 0025/2210] #15756 set metadata always and fix hadPreReleaseVersion --- .../node/extensionManagementService.ts | 2 +- .../node/extensionsScanner.ts | 22 +++++++++---------- .../common/webExtensionManagementService.ts | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index a68c9b48d8c43..7dd62de58fab4 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -288,7 +288,7 @@ class InstallGalleryExtensionTask extends AbstractInstallExtensionTask { installableExtension.metadata.isMachineScoped = this.options.isMachineScoped || existingExtension?.isMachineScoped; installableExtension.metadata.isBuiltin = this.options.isBuiltin || existingExtension?.isBuiltin; installableExtension.metadata.isPreReleaseVersion = this.gallery.properties.isPreReleaseVersion; - installableExtension.metadata.hadPreReleaseVersion = this.gallery.properties.isPreReleaseVersion || existingExtension?.hadPreReleaseVersion; + installableExtension.metadata.hadPreReleaseVersion = this.gallery.hasPreReleaseVersion ? this.gallery.properties.isPreReleaseVersion : existingExtension?.hadPreReleaseVersion; try { const local = await this.installExtension(installableExtension, token); diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index 2453fea7afee2..35c38c4820aca 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -299,9 +299,7 @@ export class ExtensionsScanner extends Disposable { const changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource; const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; const local = { type, identifier, manifest, location: extensionLocation, readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isMachineScoped: false, isBuiltin: type === ExtensionType.System }; - if (metadata) { - this.setMetadata(local, metadata); - } + this.setMetadata(local, metadata); return local; } } catch (e) { @@ -329,15 +327,15 @@ export class ExtensionsScanner extends Disposable { } } - private setMetadata(local: IRelaxedLocalExtension, metadata: Metadata): void { - local.publisherDisplayName = metadata.publisherDisplayName || null; - local.publisherId = metadata.publisherId || null; - local.identifier.uuid = metadata.id; - local.isMachineScoped = !!metadata.isMachineScoped; - local.isPreReleaseVersion = !!metadata.isPreReleaseVersion; - local.hadPreReleaseVersion = !!metadata.hadPreReleaseVersion; - local.isBuiltin = local.type === ExtensionType.System || !!metadata.isBuiltin; - local.installedTimestamp = metadata.installedTimestamp; + private setMetadata(local: IRelaxedLocalExtension, metadata: Metadata | null): void { + local.publisherDisplayName = metadata?.publisherDisplayName || null; + local.publisherId = metadata?.publisherId || null; + local.identifier.uuid = metadata?.id; + local.isMachineScoped = !!metadata?.isMachineScoped; + local.isPreReleaseVersion = !!metadata?.isPreReleaseVersion; + local.hadPreReleaseVersion = !!metadata?.hadPreReleaseVersion; + local.isBuiltin = local.type === ExtensionType.System || !!metadata?.isBuiltin; + local.installedTimestamp = metadata?.installedTimestamp; } private async removeUninstalledExtensions(): Promise { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index ea8fd42829d2a..10e3633def831 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -152,7 +152,7 @@ class InstallExtensionTask extends AbstractExtensionTask implem metadata.publisherId = this.extension.publisherId; metadata.installedTimestamp = Date.now(); metadata.isPreReleaseVersion = this.extension.properties.isPreReleaseVersion; - metadata.hadPreReleaseVersion = this.extension.properties.isPreReleaseVersion || metadata.hadPreReleaseVersion; + metadata.hadPreReleaseVersion = this.extension.hasPreReleaseVersion ? this.extension.properties.isPreReleaseVersion : metadata.hadPreReleaseVersion; } const scannedExtension = URI.isUri(this.extension) ? await this.webExtensionsScannerService.addExtension(this.extension, metadata) From a74b70781e6205c8ff58904e2399dcdf62902908 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 24 Nov 2021 23:37:54 +0100 Subject: [PATCH 0026/2210] #15756 sync prereleases --- .../userDataSync/common/extensionsMerge.ts | 9 +- .../userDataSync/common/extensionsSync.ts | 51 ++-- .../userDataSync/common/userDataSync.ts | 1 + .../test/common/extensionsMerge.test.ts | 232 ++++++++++++++---- .../electron-sandbox/remoteExtensionsInit.ts | 4 +- .../services/userData/browser/userDataInit.ts | 2 +- 6 files changed, 226 insertions(+), 73 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index c555a33b5793d..5ea8f70a852e7 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -6,6 +6,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { deepClone, equals } from 'vs/base/common/objects'; import * as semver from 'vs/base/common/semver/semver'; +import { isUndefined } from 'vs/base/common/types'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ISyncExtension, ISyncExtensionWithVersion } from 'vs/platform/userDataSync/common/userDataSync'; @@ -89,10 +90,12 @@ export function merge(localExtensions: ISyncExtensionWithVersion[], remoteExtens const localExtension = localExtensionsMap.get(key); if (localExtension) { const remoteExtension = remoteExtensionsMap.get(key)!; + const mergedExtension = updatedInRemote ? remoteExtension : localExtension; return { - ...(updatedInRemote ? remoteExtension : localExtension), + ...mergedExtension, version: remoteExtension.version && semver.gt(remoteExtension.version, localExtension.version) ? localExtension.version : localExtension.version, - state: mergeExtensionState(localExtension, remoteExtension, lastSyncExtensionsMap?.get(key)) + state: mergeExtensionState(localExtension, remoteExtension, lastSyncExtensionsMap?.get(key)), + preRelease: isUndefined(mergedExtension.preRelease) /* from older client*/ ? localExtension.preRelease : mergedExtension.preRelease }; } @@ -210,6 +213,7 @@ function compare(from: Map | null, to: Map(extension: T, key: s id: extension.identifier.id, uuid: key.startsWith('uuid:') ? key.substring('uuid:'.length) : undefined }, + preRelease: !!extension.preRelease /* set it always so that to differentiate with older clients */ }; if (extension.version) { massagedExtension.version = extension.version; diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 86563619e8ec9..b22e2e22e3037 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -364,21 +364,24 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (e.state && installedExtension.manifest.version === e.version) { this.updateExtensionState(e.state, installedExtension.manifest.publisher, installedExtension.manifest.name, installedExtension.manifest.version); } - if (e.disabled) { - this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id); - await this.extensionEnablementService.disableExtension(e.identifier); - this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id); - } else { - this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id); - await this.extensionEnablementService.enableExtension(e.identifier); - this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id); + const isDisabled = this.extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); + if (isDisabled !== !!e.disabled) { + if (e.disabled) { + this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id); + await this.extensionEnablementService.disableExtension(e.identifier); + this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id); + } else { + this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id); + await this.extensionEnablementService.enableExtension(e.identifier); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id); + } } removeFromSkipped.push(e.identifier); return; } // User Extension Sync: Install/Update, Enablement & State - const extension = (await this.extensionGalleryService.getExtensions([e.identifier], CancellationToken.None))[0]; + const extension = (await this.extensionGalleryService.getExtensions([e.identifier], !!e.preRelease, CancellationToken.None))[0]; /* Update extension state only if * extension is installed and version is same as synced version or @@ -395,21 +398,25 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (extension) { try { - if (e.disabled) { - this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version); - await this.extensionEnablementService.disableExtension(extension.identifier); - this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version); - } else { - this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version); - await this.extensionEnablementService.enableExtension(extension.identifier); - this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version); + const isDisabled = this.extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); + if (isDisabled !== !!e.disabled) { + if (e.disabled) { + this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version); + await this.extensionEnablementService.disableExtension(extension.identifier); + this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version); + } else { + this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version); + await this.extensionEnablementService.enableExtension(extension.identifier); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version); + } } - // Install only if the extension does not exist - if (!installedExtension) { + if (!installedExtension // Install if the extension does not exist + || installedExtension.hadPreReleaseVersion !== e.preRelease // Install if the extension pre-release preference has changed + ) { if (await this.extensionManagementService.canInstall(extension)) { this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version); - await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true } /* pass options to prevent install and sync dialog in web */); + await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease } /* pass options to prevent install and sync dialog in web */); this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version); removeFromSkipped.push(extension.identifier); } else { @@ -465,8 +472,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private getLocalExtensions(installedExtensions: ILocalExtension[]): ISyncExtensionWithVersion[] { const disabledExtensions = this.extensionEnablementService.getDisabledExtensions(); return installedExtensions - .map(({ identifier, isBuiltin, manifest }) => { - const syncExntesion: ISyncExtensionWithVersion = { identifier, version: manifest.version }; + .map(({ identifier, isBuiltin, manifest, hadPreReleaseVersion }) => { + const syncExntesion: ISyncExtensionWithVersion = { identifier, version: manifest.version, preRelease: hadPreReleaseVersion }; if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { syncExntesion.disabled = true; } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index ec3e7705e02e5..215b0701e2c92 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -301,6 +301,7 @@ export namespace UserDataSyncError { export interface ISyncExtension { identifier: IExtensionIdentifier; + preRelease?: boolean; version?: string; disabled?: boolean; installed?: boolean; diff --git a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index 79ff96ea83259..356acd8c4cd54 100644 --- a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -117,15 +117,15 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); assert.deepStrictEqual(actual.remote?.all, expected); @@ -141,14 +141,14 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], ['a']); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); assert.deepStrictEqual(actual.remote?.all, expected); @@ -170,7 +170,7 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]); assert.deepStrictEqual(actual.local.updated, []); assert.strictEqual(actual.remote, null); @@ -193,9 +193,9 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, [{ id: 'a', uuid: 'a' }]); - assert.deepStrictEqual(actual.local.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true, version: '1.0.0', preRelease: false }]); assert.strictEqual(actual.remote, null); }); @@ -215,7 +215,7 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a']); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, [{ id: 'd', uuid: 'd' }]); assert.deepStrictEqual(actual.local.updated, []); assert.strictEqual(actual.remote, null); @@ -239,7 +239,7 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, [{ id: 'd', uuid: 'd' }]); assert.deepStrictEqual(actual.local.updated, []); assert.strictEqual(actual.remote, null); @@ -263,7 +263,7 @@ suite('ExtensionsMerge', () => { const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, [{ id: 'd', uuid: 'd' }]); assert.deepStrictEqual(actual.local.updated, []); assert.strictEqual(actual.remote, null); @@ -282,13 +282,17 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' }, { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, ]; + const expected: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, + ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); assert.deepStrictEqual(actual.local.added, []); assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); - assert.deepStrictEqual(actual.remote?.all, localExtensions); + assert.deepStrictEqual(actual.remote?.all, expected); }); test('merge local and remote extensions when local is moved forwarded with disabled extensions', () => { @@ -305,13 +309,18 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' }, { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, ]; + const expected: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, disabled: true, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, + ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); assert.deepStrictEqual(actual.local.added, []); assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); - assert.deepStrictEqual(actual.remote?.all, localExtensions); + assert.deepStrictEqual(actual.remote?.all, expected); }); test('merge local and remote extensions when local is moved forwarded with ignored settings', () => { @@ -334,7 +343,7 @@ suite('ExtensionsMerge', () => { assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); assert.deepStrictEqual(actual.remote?.all, [ - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, ]); }); @@ -355,9 +364,9 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); @@ -385,8 +394,8 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']); @@ -413,14 +422,14 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, [{ id: 'a', uuid: 'a' }]); assert.deepStrictEqual(actual.local.updated, []); assert.deepStrictEqual(actual.remote?.all, expected); @@ -442,9 +451,9 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']); @@ -473,14 +482,14 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); assert.deepStrictEqual(actual.remote?.all, expected); @@ -504,9 +513,9 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']); @@ -528,15 +537,15 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'A', uuid: 'a' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, - { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }, + { identifier: { id: 'A', uuid: 'a' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0', preRelease: false }, + { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }]); + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0', preRelease: false }]); assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); assert.deepStrictEqual(actual.remote?.all, expected); @@ -566,13 +575,16 @@ suite('ExtensionsMerge', () => { const remoteExtensions: ISyncExtensionWithVersion[] = [ { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0' }, ]; + const expected: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0', preRelease: false }, + ]; const actual = merge(localExtensions, remoteExtensions, null, [], []); assert.deepStrictEqual(actual.local.added, []); assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); - assert.deepStrictEqual(actual.remote?.all, localExtensions); + assert.deepStrictEqual(actual.remote?.all, expected); }); test('merge when an extension is not an installed extension remotely and does not exist locally', () => { @@ -600,7 +612,7 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true, version: '1.0.0' }, + { identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []); @@ -623,7 +635,9 @@ suite('ExtensionsMerge', () => { assert.deepStrictEqual(actual.local.added, []); assert.deepStrictEqual(actual.local.removed, []); - assert.deepStrictEqual(actual.local.updated, remoteExtensions); + assert.deepStrictEqual(actual.local.updated, [ + { identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true, version: '1.0.0', preRelease: false }, + ]); assert.deepStrictEqual(actual.remote, null); }); @@ -635,8 +649,8 @@ suite('ExtensionsMerge', () => { { identifier: { id: 'b', uuid: 'b' }, version: '1.0.0' }, ]; const expected: ISyncExtensionWithVersion[] = [ - { identifier: { id: 'b', uuid: 'b' }, version: '1.0.0' }, - { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0' }, + { identifier: { id: 'b', uuid: 'b' }, version: '1.0.0', preRelease: false }, + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', preRelease: false }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], []); @@ -647,4 +661,128 @@ suite('ExtensionsMerge', () => { assert.deepStrictEqual(actual.remote?.all, expected); }); + test('merge: remote extension with prerelease is added', () => { + const localExtensions: ISyncExtensionWithVersion[] = []; + const remoteExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, null, [], []); + + assert.deepStrictEqual(actual.local.added, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }]); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote, null); + }); + + test('merge: local extension with prerelease is added', () => { + const localExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }, + ]; + const remoteExtensions: ISyncExtensionWithVersion[] = []; + + const actual = merge(localExtensions, remoteExtensions, null, [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote?.all, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }]); + }); + + test('merge: remote extension with prerelease is added when local extension without prerelease is added', () => { + const localExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true }, + ]; + const remoteExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, null, [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }]); + assert.deepStrictEqual(actual.remote, null); + }); + + test('merge: remote extension without prerelease is added when local extension with prerelease is added', () => { + const localExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }, + ]; + const remoteExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, null, [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }]); + assert.deepStrictEqual(actual.remote?.all, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }]); + }); + + test('merge: remote extension is changed to prerelease', () => { + const localExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true }, + ]; + const remoteExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, localExtensions, [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }]); + assert.deepStrictEqual(actual.remote, null); + }); + + test('merge: remote extension is changed to release', () => { + const localExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }, + ]; + const remoteExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: false }, + ]; + + const actual = merge(localExtensions, remoteExtensions, localExtensions, [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: false }]); + assert.deepStrictEqual(actual.remote, null); + }); + + test('merge: local extension is changed to prerelease', () => { + const localExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }, + ]; + const remoteExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote?.all, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }]); + }); + + test('merge: local extension is changed to release', () => { + const localExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: false }, + ]; + const remoteExtensions: ISyncExtensionWithVersion[] = [ + { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote?.all, [{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', installed: true, preRelease: false }]); + }); + }); diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts index d1d29adc444db..8c65ebcc1bc93 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts @@ -6,6 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -124,7 +125,8 @@ class RemoteExtensionsInitializer extends AbstractExtensionsInitializer { await Promise.allSettled(extensionsToInstall.map(async e => { const manifest = await this.extensionGalleryService.getManifest(e, CancellationToken.None); if (manifest && this.extensionManifestPropertiesService.canExecuteOnWorkspace(manifest)) { - await this.extensionManagementService.installFromGallery(e); + const syncedExtension = remoteExtensions.find(e => areSameExtensions(e.identifier, e.identifier)); + await this.extensionManagementService.installFromGallery(e, { installPreReleaseVersion: syncedExtension?.preRelease, donotIncludePackAndDependencies: true }); } })); } diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index ecd61071e2681..5dbf5aa9f64b0 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -395,7 +395,7 @@ class NewExtensionsInitializer implements IUserDataInitializer { storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService); } this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); - const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false, donotIncludePackAndDependencies: true } /* pass options to prevent install and sync dialog in web */); + const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: extensionToSync.preRelease } /* pass options to prevent install and sync dialog in web */); if (!preview.disabledExtensions.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { newlyEnabledExtensions.push(local); } From 4947eba7a5e7160d937115ee4030671182d8aca7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 24 Nov 2021 23:42:19 +0100 Subject: [PATCH 0027/2210] #15756 rename --- .../common/abstractExtensionManagementService.ts | 2 +- .../extensionManagement/common/extensionManagement.ts | 2 +- .../extensionManagement/node/extensionManagementService.ts | 2 +- .../platform/extensionManagement/node/extensionsScanner.ts | 2 +- src/vs/platform/userDataSync/common/extensionsSync.ts | 6 +++--- .../extensions/browser/extensionsWorkbenchService.ts | 2 +- .../common/webExtensionManagementService.ts | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 0329c7b9d4a72..ea58b5a4aba9f 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -22,7 +22,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export type Metadata = Partial; +export type Metadata = Partial; export interface IInstallExtensionTask { readonly identifier: IExtensionIdentifier; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index a591a56cc0d4f..3d7442a342389 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -274,7 +274,7 @@ export interface ILocalExtension extends IExtension { publisherDisplayName: string | null; installedTimestamp?: number; isPreReleaseVersion: boolean; - hadPreReleaseVersion: boolean; + preRelease: boolean; } export const enum SortBy { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 7dd62de58fab4..0eb79df9a4fe4 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -288,7 +288,7 @@ class InstallGalleryExtensionTask extends AbstractInstallExtensionTask { installableExtension.metadata.isMachineScoped = this.options.isMachineScoped || existingExtension?.isMachineScoped; installableExtension.metadata.isBuiltin = this.options.isBuiltin || existingExtension?.isBuiltin; installableExtension.metadata.isPreReleaseVersion = this.gallery.properties.isPreReleaseVersion; - installableExtension.metadata.hadPreReleaseVersion = this.gallery.hasPreReleaseVersion ? this.gallery.properties.isPreReleaseVersion : existingExtension?.hadPreReleaseVersion; + installableExtension.metadata.preRelease = this.gallery.hasPreReleaseVersion ? this.gallery.properties.isPreReleaseVersion : existingExtension?.preRelease; try { const local = await this.installExtension(installableExtension, token); diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index 35c38c4820aca..a11c6aa1d27f2 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -333,7 +333,7 @@ export class ExtensionsScanner extends Disposable { local.identifier.uuid = metadata?.id; local.isMachineScoped = !!metadata?.isMachineScoped; local.isPreReleaseVersion = !!metadata?.isPreReleaseVersion; - local.hadPreReleaseVersion = !!metadata?.hadPreReleaseVersion; + local.preRelease = !!metadata?.preRelease; local.isBuiltin = local.type === ExtensionType.System || !!metadata?.isBuiltin; local.installedTimestamp = metadata?.installedTimestamp; } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index b22e2e22e3037..7124a8fd052b8 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -412,7 +412,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } if (!installedExtension // Install if the extension does not exist - || installedExtension.hadPreReleaseVersion !== e.preRelease // Install if the extension pre-release preference has changed + || installedExtension.preRelease !== e.preRelease // Install if the extension pre-release preference has changed ) { if (await this.extensionManagementService.canInstall(extension)) { this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version); @@ -472,8 +472,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private getLocalExtensions(installedExtensions: ILocalExtension[]): ISyncExtensionWithVersion[] { const disabledExtensions = this.extensionEnablementService.getDisabledExtensions(); return installedExtensions - .map(({ identifier, isBuiltin, manifest, hadPreReleaseVersion }) => { - const syncExntesion: ISyncExtensionWithVersion = { identifier, version: manifest.version, preRelease: hadPreReleaseVersion }; + .map(({ identifier, isBuiltin, manifest, preRelease }) => { + const syncExntesion: ISyncExtensionWithVersion = { identifier, version: manifest.version, preRelease }; if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { syncExntesion.disabled = true; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 7526f30a55dd3..7e33ad4a6405a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1019,7 +1019,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension (this.getAutoUpdateValue() === true || (e.local && this.extensionEnablementService.isEnabled(e.local))) ); - return Promises.settled(toUpdate.map(e => this.install(e, e.local?.hadPreReleaseVersion ? { installPreReleaseVersion: true } : undefined))); + return Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true } : undefined))); } async canInstall(extension: IExtension): Promise { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 10e3633def831..1ea8f90cc6cb5 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -110,7 +110,7 @@ function toLocalExtension(extension: IScannedExtension): ILocalExtension { publisherDisplayName: metadata.publisherDisplayName || null, installedTimestamp: metadata.installedTimestamp, isPreReleaseVersion: !!metadata.isPreReleaseVersion, - hadPreReleaseVersion: !!metadata.hadPreReleaseVersion + preRelease: !!metadata.preRelease }; } @@ -152,7 +152,7 @@ class InstallExtensionTask extends AbstractExtensionTask implem metadata.publisherId = this.extension.publisherId; metadata.installedTimestamp = Date.now(); metadata.isPreReleaseVersion = this.extension.properties.isPreReleaseVersion; - metadata.hadPreReleaseVersion = this.extension.hasPreReleaseVersion ? this.extension.properties.isPreReleaseVersion : metadata.hadPreReleaseVersion; + metadata.preRelease = this.extension.hasPreReleaseVersion ? this.extension.properties.isPreReleaseVersion : metadata.preRelease; } const scannedExtension = URI.isUri(this.extension) ? await this.webExtensionsScannerService.addExtension(this.extension, metadata) From 60c59ff641b2d0a378aafab55dc311eafb751008 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 24 Nov 2021 15:07:15 -0800 Subject: [PATCH 0028/2210] Revert "trigger didChange event when you update inputbox.value. ref c1c103dee729b6f702c1c5272d25b24ea9664d48" This reverts commit bdc6162b1d40bfa5220490ddbc8369d2abc74481. --- src/vs/base/parts/quickinput/browser/quickInput.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 9fbbf2ec5f612..867c93a2df55c 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -1061,6 +1061,7 @@ class QuickPick extends QuickInput implements IQuickPi } class InputBox extends QuickInput implements IInputBox { + private _value = ''; private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; private _placeholder: string | undefined; @@ -1070,11 +1071,12 @@ class InputBox extends QuickInput implements IInputBox { private readonly onDidAcceptEmitter = this._register(new Emitter()); get value() { - return this.ui.inputBox.value; + return this._value; } set value(value: string) { - this.ui.inputBox.value = value ?? ''; + this._value = value || ''; + this.update(); } set valueSelection(valueSelection: Readonly<[number, number]>) { @@ -1121,6 +1123,10 @@ class InputBox extends QuickInput implements IInputBox { if (!this.visible) { this.visibleDisposables.add( this.ui.inputBox.onDidChange(value => { + if (value === this.value) { + return; + } + this._value = value; this.onDidValueChangeEmitter.fire(value); })); this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire())); @@ -1140,6 +1146,9 @@ class InputBox extends QuickInput implements IInputBox { }; this.ui.setVisibilities(visibilities); super.update(); + if (this.ui.inputBox.value !== this.value) { + this.ui.inputBox.value = this.value; + } if (this.valueSelectionUpdated) { this.valueSelectionUpdated = false; this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] }); From 26fe37ca3d9dcc934ffcacd82443a38c9b192dd0 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 24 Nov 2021 15:08:35 -0800 Subject: [PATCH 0029/2210] Revert "Have setting the value trigger the filter. Fixes #137279" This reverts commit c1c103dee729b6f702c1c5272d25b24ea9664d48. --- .../src/singlefolder-tests/quickInput.test.ts | 146 ------------------ .../parts/quickinput/browser/quickInput.ts | 5 - .../test/browser/quickAccess.test.ts | 3 +- 3 files changed, 1 insertion(+), 153 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index bf0da5076b180..ba7ce21e32f02 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -11,7 +11,6 @@ interface QuickPickExpected { events: string[]; activeItems: string[][]; selectionItems: string[][]; - values: string[]; acceptedItems: { active: string[][]; selection: string[][]; @@ -19,15 +18,6 @@ interface QuickPickExpected { }; } -interface InputBoxExpected { - events: string[]; - values: string[]; - accepted: { - values: string[]; - dispose: boolean[]; - }; -} - suite('vscode API - quick input', function () { teardown(async function () { @@ -45,7 +35,6 @@ suite('vscode API - quick input', function () { events: ['active', 'active', 'selection', 'accept', 'hide'], activeItems: [['eins'], ['zwei']], selectionItems: [['zwei']], - values: [], acceptedItems: { active: [['zwei']], selection: [['zwei']], @@ -72,7 +61,6 @@ suite('vscode API - quick input', function () { events: ['active', 'selection', 'accept', 'hide'], activeItems: [['zwei']], selectionItems: [['zwei']], - values: [], acceptedItems: { active: [['zwei']], selection: [['zwei']], @@ -99,7 +87,6 @@ suite('vscode API - quick input', function () { events: ['active', 'selection', 'active', 'selection', 'accept', 'hide'], activeItems: [['eins'], ['zwei']], selectionItems: [['eins'], ['eins', 'zwei']], - values: [], acceptedItems: { active: [['zwei']], selection: [['eins', 'zwei']], @@ -130,7 +117,6 @@ suite('vscode API - quick input', function () { events: ['active', 'selection', 'accept', 'selection', 'accept', 'hide'], activeItems: [['eins']], selectionItems: [['zwei'], ['drei']], - values: [], acceptedItems: { active: [['eins'], ['eins']], selection: [['zwei'], ['drei']], @@ -156,7 +142,6 @@ suite('vscode API - quick input', function () { events: ['active', 'selection', 'accept', 'active', 'selection', 'active', 'selection', 'accept', 'hide'], activeItems: [['eins'], [], ['drei']], selectionItems: [['eins'], [], ['drei']], - values: [], acceptedItems: { active: [['eins'], ['drei']], selection: [['eins'], ['drei']], @@ -178,40 +163,6 @@ suite('vscode API - quick input', function () { .catch(err => done(err)); }); - // NOTE: This test is currently accepting the wrong behavior of #135971 - // so that we can test the fix for #137279. - test('createQuickPick, onDidChangeValue gets triggered', function (_done) { - let done = (err?: any) => { - done = () => { }; - _done(err); - }; - - const quickPick = createQuickPick({ - events: ['active', 'active', 'active', 'active', 'value', 'active', 'active', 'value', 'hide'], - activeItems: [['eins'], ['zwei'], [], ['zwei'], [], ['eins']], - selectionItems: [], - values: ['zwei', ''], - acceptedItems: { - active: [], - selection: [], - dispose: [] - }, - }, (err?: any) => done(err)); - quickPick.items = ['eins', 'zwei'].map(label => ({ label })); - quickPick.show(); - - (async () => { - quickPick.value = 'zwei'; - await timeout(async () => { - quickPick.value = ''; - await timeout(async () => { - quickPick.hide(); - }, 0); - }, 0); - })() - .catch(err => done(err)); - }); - test('createQuickPick, dispose in onDidHide', function (_done) { let done = (err?: any) => { done = () => { }; @@ -297,34 +248,6 @@ suite('vscode API - quick input', function () { quickPick.hide(); await waitForHide(quickPick); }); - - test('createInputBox, onDidChangeValue gets triggered', function (_done) { - let done = (err?: any) => { - done = () => { }; - _done(err); - }; - - const quickPick = createInputBox({ - events: ['value', 'accept', 'hide'], - values: ['zwei'], - accepted: { - values: ['zwei'], - dispose: [true] - }, - }, (err?: any) => done(err)); - quickPick.show(); - - (async () => { - quickPick.value = 'zwei'; - await timeout(async () => { - await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); - await timeout(async () => { - quickPick.hide(); - }, 0); - }, 0); - })() - .catch(err => done(err)); - }); }); function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, record = false) { @@ -393,78 +316,9 @@ function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, } }); - quickPick.onDidChangeValue(value => { - if (record) { - console.log('value'); - return; - } - - try { - eventIndex++; - assert.strictEqual('value', expected.events.shift(), `onDidChangeValue (event ${eventIndex})`); - const expectedValue = expected.values.shift(); - assert.deepStrictEqual(value, expectedValue, `onDidChangeValue event value (event ${eventIndex})`); - } catch (err) { - done(err); - } - }); - return quickPick; } -function createInputBox(expected: InputBoxExpected, done: (err?: any) => void, record = false) { - const inputBox = window.createInputBox(); - let eventIndex = -1; - inputBox.onDidAccept(() => { - if (record) { - console.log('accept'); - return; - } - try { - eventIndex++; - assert.strictEqual('accept', expected.events.shift(), `onDidAccept (event ${eventIndex})`); - const expectedValue = expected.accepted.values.shift(); - assert.deepStrictEqual(inputBox.value, expectedValue, `onDidAccept event value (event ${eventIndex})`); - if (expected.accepted.dispose.shift()) { - inputBox.dispose(); - } - } catch (err) { - done(err); - } - }); - inputBox.onDidHide(() => { - if (record) { - console.log('hide'); - done(); - return; - } - try { - assert.strictEqual('hide', expected.events.shift()); - done(); - } catch (err) { - done(err); - } - }); - - inputBox.onDidChangeValue(value => { - if (record) { - console.log('value'); - return; - } - - try { - eventIndex++; - assert.strictEqual('value', expected.events.shift(), `onDidChangeValue (event ${eventIndex})`); - const expectedValue = expected.values.shift(); - assert.deepStrictEqual(value, expectedValue, `onDidChangeValue event value (event ${eventIndex})`); - } catch (err) { - done(err); - } - }); - - return inputBox; -} - async function timeout(run: () => Promise | T, ms: number): Promise { return new Promise(resolve => setTimeout(() => resolve(run()), ms)); } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 867c93a2df55c..4a9a2db8cd2ce 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -490,11 +490,6 @@ class QuickPick extends QuickInput implements IQuickPi if (this._value !== value) { this._value = value || ''; this.update(); - // TODO: Remove this duplicate code and have the updating of the input box handle this. - const didFilter = this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); - if (didFilter) { - this.trySelectFirst(); - } this.onDidChangeValueEmitter.fire(this._value); } } diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts index 587a2fa9bacef..3130a990ac5b8 100644 --- a/src/vs/workbench/test/browser/quickAccess.test.ts +++ b/src/vs/workbench/test/browser/quickAccess.test.ts @@ -66,8 +66,7 @@ suite('QuickAccess', () => { provide(picker: IQuickPick, token: CancellationToken): IDisposable { assert.ok(picker); provider2Called = true; - token.onCancellationRequested(() => - provider2Canceled = true); + token.onCancellationRequested(() => provider2Canceled = true); return toDisposable(() => provider2Disposed = true); } From 9ec1ae1d927625a41555c1a655dafa1da1f78550 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 24 Nov 2021 16:40:39 -0800 Subject: [PATCH 0030/2210] silently getting session should return a session (if any) regardless of preference. fixes #137819 --- .../api/browser/mainThreadAuthentication.ts | 15 ++- .../browser/authenticationService.ts | 106 +++++++++--------- .../browser/api/extHostAuthentication.test.ts | 70 ++++++++++-- 3 files changed, 123 insertions(+), 68 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 37dfb7e300724..34c5b4f490d85 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -207,6 +207,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu private async doGetSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { const sessions = await this.authenticationService.getSessions(providerId, scopes, true); + const supportsMultipleAccounts = this.authenticationService.supportsMultipleAccounts(providerId); // Error cases if (options.forceNewSession && !sessions.length) { @@ -224,7 +225,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu // Check if the sessions we have are valid if (!options.forceNewSession && sessions.length) { - if (this.authenticationService.supportsMultipleAccounts(providerId)) { + if (supportsMultipleAccounts) { if (options.clearSessionPreference) { this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL); } else { @@ -251,18 +252,20 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu throw new Error('User did not consent to login.'); } - const session = sessions?.length && !options.forceNewSession + const session = sessions?.length && !options.forceNewSession && supportsMultipleAccounts ? await this.authenticationService.selectSession(providerId, extensionId, extensionName, scopes, sessions) : await this.authenticationService.createSession(providerId, scopes, true); await this.setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id); return session; } - // passive flows - if (!options.silent) { + + // passive flows (silent or default) + + const validSession = sessions.find(s => this.authenticationService.isAccessAllowed(providerId, s.account.label, extensionId)); + if (!options.silent && !validSession) { await this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName); } - - return undefined; + return validSession; } async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 0888f24f9d36b..1d81ce77b1028 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -614,68 +614,70 @@ export class AuthenticationService extends Disposable implements IAuthentication }); } - if (provider) { - const providerRequests = this._signInRequestItems.get(providerId); - const scopesList = scopes.join(SCOPESLIST_SEPARATOR); - const extensionHasExistingRequest = providerRequests - && providerRequests[scopesList] - && providerRequests[scopesList].requestingExtensionIds.includes(extensionId); - - if (extensionHasExistingRequest) { - return; - } + if (!provider) { + return; + } - // Construct a commandId that won't clash with others generated here, nor likely with an extension's command - const commandId = `${providerId}:${extensionId}:signIn${Object.keys(providerRequests || []).length}`; - const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { - group: '2_signInRequests', - command: { - id: commandId, - title: nls.localize({ - key: 'signInRequest', - comment: [`The placeholder {0} will be replaced with an authentication provider's label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count.`] - }, - "Sign in with {0} to use {1} (1)", - provider.label, - extensionName) - } - }); + const providerRequests = this._signInRequestItems.get(providerId); + const scopesList = scopes.join(SCOPESLIST_SEPARATOR); + const extensionHasExistingRequest = providerRequests + && providerRequests[scopesList] + && providerRequests[scopesList].requestingExtensionIds.includes(extensionId); + + if (extensionHasExistingRequest) { + return; + } - const signInCommand = CommandsRegistry.registerCommand({ + // Construct a commandId that won't clash with others generated here, nor likely with an extension's command + const commandId = `${providerId}:${extensionId}:signIn${Object.keys(providerRequests || []).length}`; + const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + group: '2_signInRequests', + command: { id: commandId, - handler: async (accessor) => { - const authenticationService = accessor.get(IAuthenticationService); - const storageService = accessor.get(IStorageService); - const session = await authenticationService.createSession(providerId, scopes); + title: nls.localize({ + key: 'signInRequest', + comment: [`The placeholder {0} will be replaced with an authentication provider's label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count.`] + }, + "Sign in with {0} to use {1} (1)", + provider.label, + extensionName) + } + }); - // Add extension to allow list since user explicitly signed in on behalf of it - this.updatedAllowedExtension(providerId, session.account.label, extensionId, extensionName, true); + const signInCommand = CommandsRegistry.registerCommand({ + id: commandId, + handler: async (accessor) => { + const authenticationService = accessor.get(IAuthenticationService); + const storageService = accessor.get(IStorageService); + const session = await authenticationService.createSession(providerId, scopes); - // And also set it as the preferred account for the extension - storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE); - } - }); + // Add extension to allow list since user explicitly signed in on behalf of it + this.updatedAllowedExtension(providerId, session.account.label, extensionId, extensionName, true); + // And also set it as the preferred account for the extension + storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE); + } + }); - if (providerRequests) { - const existingRequest = providerRequests[scopesList] || { disposables: [], requestingExtensionIds: [] }; - providerRequests[scopesList] = { - disposables: [...existingRequest.disposables, menuItem, signInCommand], - requestingExtensionIds: [...existingRequest.requestingExtensionIds, extensionId] - }; - this._signInRequestItems.set(providerId, providerRequests); - } else { - this._signInRequestItems.set(providerId, { - [scopesList]: { - disposables: [menuItem, signInCommand], - requestingExtensionIds: [extensionId] - } - }); - } + if (providerRequests) { + const existingRequest = providerRequests[scopesList] || { disposables: [], requestingExtensionIds: [] }; - this.updateBadgeCount(); + providerRequests[scopesList] = { + disposables: [...existingRequest.disposables, menuItem, signInCommand], + requestingExtensionIds: [...existingRequest.requestingExtensionIds, extensionId] + }; + this._signInRequestItems.set(providerId, providerRequests); + } else { + this._signInRequestItems.set(providerId, { + [scopesList]: { + disposables: [menuItem, signInCommand], + requestingExtensionIds: [extensionId] + } + }); } + + this.updateBadgeCount(); } getLabel(id: string): string { const authProvider = this._authenticationProviders.get(id); diff --git a/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts b/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts index 260ea31ca986a..5c89130cbb889 100644 --- a/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts +++ b/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts @@ -56,6 +56,7 @@ class AuthTestQuickInputService extends TestQuickInputService { } class TestAuthProvider implements AuthenticationProvider { + private id = 1; private sessions = new Map(); onDidChangeSessions = () => { return { dispose() { } }; }; async getSessions(scopes?: readonly string[]): Promise { @@ -73,14 +74,15 @@ class TestAuthProvider implements AuthenticationProvider { const scopesStr = scopes.join(' '); const session = { scopes, - id: 'test' + scopesStr, + id: `${this.id}`, account: { - label: scopesStr, - id: scopesStr, + label: `${this.id}`, + id: `${this.id}`, }, accessToken: Math.random() + '', }; this.sessions.set(scopesStr, session); + this.id++; return session; } async removeSession(sessionId: string): Promise { @@ -137,7 +139,7 @@ suite('ExtHostAuthentication', () => { { createIfNone: true }); - assert.strictEqual(session?.id, 'test' + scopes.join(' ')); + assert.strictEqual(session?.id, '1'); assert.strictEqual(session?.scopes[0], 'foo'); }); @@ -159,7 +161,7 @@ suite('ExtHostAuthentication', () => { createIfNone: true }); - assert.strictEqual(session?.id, 'test' + scopes.join(' ')); + assert.strictEqual(session?.id, '1'); assert.strictEqual(session?.scopes[0], 'foo'); const session2 = await extHostAuthentication.getSession( @@ -194,7 +196,7 @@ suite('ExtHostAuthentication', () => { createIfNone: true }); - assert.strictEqual(session?.id, 'test' + scopes.join(' ')); + assert.strictEqual(session?.id, '1'); assert.strictEqual(session?.scopes[0], 'foo'); const session2 = await extHostAuthentication.getSession( @@ -228,7 +230,7 @@ suite('ExtHostAuthentication', () => { forceNewSession: true }); - assert.strictEqual(session2?.id, 'test' + scopes.join(' ')); + assert.strictEqual(session2?.id, '2'); assert.strictEqual(session2?.scopes[0], 'foo'); assert.notStrictEqual(session1.accessToken, session2?.accessToken); }); @@ -252,11 +254,13 @@ suite('ExtHostAuthentication', () => { forceNewSession: { detail: 'bar' } }); - assert.strictEqual(session2?.id, 'test' + scopes.join(' ')); + assert.strictEqual(session2?.id, '2'); assert.strictEqual(session2?.scopes[0], 'foo'); assert.notStrictEqual(session1.accessToken, session2?.accessToken); }); + //#region Multi-Account AuthProvider + test('clearSessionPreference - true', async () => { const scopes = ['foo']; // Now create the session @@ -268,7 +272,7 @@ suite('ExtHostAuthentication', () => { createIfNone: true }); - assert.strictEqual(session?.id, 'test' + scopes.join(' ')); + assert.strictEqual(session?.id, '1'); assert.strictEqual(session?.scopes[0], scopes[0]); const scopes2 = ['bar']; @@ -279,7 +283,7 @@ suite('ExtHostAuthentication', () => { { createIfNone: true }); - assert.strictEqual(session2?.id, 'test' + scopes2.join(' ')); + assert.strictEqual(session2?.id, '2'); assert.strictEqual(session2?.scopes[0], scopes2[0]); const session3 = await extHostAuthentication.getSession( @@ -298,6 +302,52 @@ suite('ExtHostAuthentication', () => { assert.strictEqual(session.accessToken, session3?.accessToken); }); + test('silently getting session should return a session (if any) regardless of preference - fixes #137819', async () => { + const scopes = ['foo']; + // Now create the session + const session = await extHostAuthentication.getSession( + extensionDescription, + 'test-multiple', + scopes, + { + createIfNone: true + }); + + assert.strictEqual(session?.id, '1'); + assert.strictEqual(session?.scopes[0], scopes[0]); + + const scopes2 = ['bar']; + const session2 = await extHostAuthentication.getSession( + extensionDescription, + 'test-multiple', + scopes2, + { + createIfNone: true + }); + assert.strictEqual(session2?.id, '2'); + assert.strictEqual(session2?.scopes[0], scopes2[0]); + + const shouldBeSession1 = await extHostAuthentication.getSession( + extensionDescription, + 'test-multiple', + scopes, + {}); + assert.strictEqual(session.id, shouldBeSession1?.id); + assert.strictEqual(session.scopes[0], shouldBeSession1?.scopes[0]); + assert.strictEqual(session.accessToken, shouldBeSession1?.accessToken); + + const shouldBeSession2 = await extHostAuthentication.getSession( + extensionDescription, + 'test-multiple', + scopes2, + {}); + assert.strictEqual(session2.id, shouldBeSession2?.id); + assert.strictEqual(session2.scopes[0], shouldBeSession2?.scopes[0]); + assert.strictEqual(session2.accessToken, shouldBeSession2?.accessToken); + }); + + //#endregion + //#region error cases test('forceNewSession with no sessions', async () => { From 1e473b624f088fc05269891170a8ffa1c84a35a6 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 25 Nov 2021 08:40:34 +0100 Subject: [PATCH 0031/2210] fix themes. linter errors --- .../workbench/contrib/themes/browser/themes.contribution.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index d4ba184c2574f..1c26893ecd1f3 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -149,9 +149,9 @@ export class SelectColorThemeAction extends Action { mp_quickpick.items = []; mp_quickpick.sortByLabel = false; mp_quickpick.matchOnDescription = true; - mp_quickpick.buttons = [this.quickInputService.backButton] + mp_quickpick.buttons = [this.quickInputService.backButton]; mp_quickpick.title = 'Marketplace Themes'; - mp_quickpick.placeholder = localize('themes.selectTheme', "Type to Search More. Select to Install. Up/Down Keys to Preview"); + mp_quickpick.placeholder = localize('themes.selectMarketplaceTheme', "Type to Search More. Select to Install. Up/Down Keys to Preview"); mp_quickpick.canSelectMany = false; mp_quickpick.onDidChangeValue(() => marketplaceThemes.trigger(mp_quickpick.value)); mp_quickpick.onDidAccept(async _ => { @@ -209,7 +209,7 @@ export class SelectColorThemeAction extends Action { }); marketplaceThemes.trigger(value); mp_quickpick.show(); - } + }; browseInstalledThemes(currentTheme.id); }); From 648e355c05fe2b0cfd38379fea30f08d19114fc0 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 09:40:16 +0100 Subject: [PATCH 0032/2210] Add tracing to sockets --- src/vs/base/common/buffer.ts | 13 +- src/vs/base/parts/ipc/common/ipc.net.ts | 146 +++++++++++++++++- src/vs/base/parts/ipc/node/ipc.net.ts | 67 ++++++-- .../remote/browser/browserSocketFactory.ts | 61 ++++++-- .../remote/common/remoteAgentConnection.ts | 8 +- .../platform/remote/node/nodeSocketFactory.ts | 4 +- .../server/remoteExtensionHostAgentServer.ts | 4 +- .../localProcessExtensionHost.ts | 2 +- .../node/extensionHostProcessSetup.ts | 6 +- 9 files changed, 273 insertions(+), 38 deletions(-) diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index f685828119371..0e28b41904b60 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -90,11 +90,20 @@ export class VSBuffer { set(array: VSBuffer, offset?: number): void; set(array: Uint8Array, offset?: number): void; - set(array: VSBuffer | Uint8Array, offset?: number): void { + set(array: ArrayBuffer, offset?: number): void; + set(array: ArrayBufferView, offset?: number): void; + set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void; + set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void { if (array instanceof VSBuffer) { this.buffer.set(array.buffer, offset); - } else { + } else if (array instanceof Uint8Array) { this.buffer.set(array, offset); + } else if (array instanceof ArrayBuffer) { + this.buffer.set(new Uint8Array(array), offset); + } else if (ArrayBuffer.isView(array)) { + this.buffer.set(new Uint8Array(array.buffer, array.byteOffset, array.byteLength), offset); + } else { + throw new Error(`Unkown argument 'array'`); } } diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 576c91cdb5017..5075a80ec4fff 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -8,6 +8,89 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IIPCLogger, IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc'; +export const enum SocketDiagnosticsEventType { + Created = 'created', + Read = 'read', + Write = 'write', + Open = 'open', + Error = 'error', + Close = 'close', + + BrowserWebSocketBlobReceived = 'browserWebSocketBlobReceived', + + NodeEndReceived = 'nodeEndReceived', + NodeEndSent = 'nodeEndSent', + NodeDrainBegin = 'nodeDrainBegin', + NodeDrainEnd = 'nodeDrainEnd', + + zlibInflateError = 'zlibInflateError', + zlibInflateData = 'zlibInflateData', + zlibInflateWriteInitial = 'zlibInflateWriteInitial', + zlibInflateInitialFlushFired = 'zlibInflateInitialFlushFired', + zlibInflateWrite = 'zlibInflateWrite', + zlibInflateFlushFired = 'zlibInflateFlushFired', + zlibDeflateError = 'zlibDeflateError', + zlibDeflateData = 'zlibDeflateData', + zlibDeflateWrite = 'zlibDeflateWrite', + zlibDeflateFlushFired = 'zlibDeflateFlushFired', + + WebSocketNodeSocketWrite = 'webSocketNodeSocketWrite', + WebSocketNodeSocketPeekedHeader = 'webSocketNodeSocketPeekedHeader', + WebSocketNodeSocketReadHeader = 'webSocketNodeSocketReadHeader', + WebSocketNodeSocketReadData = 'webSocketNodeSocketReadData', + WebSocketNodeSocketUnmaskedData = 'webSocketNodeSocketUnmaskedData', + WebSocketNodeSocketDrainBegin = 'webSocketNodeSocketDrainBegin', + WebSocketNodeSocketDrainEnd = 'webSocketNodeSocketDrainEnd', + + ProtocolHeaderRead = 'protocolHeaderRead', + ProtocolMessageRead = 'protocolMessageRead', + ProtocolHeaderWrite = 'protocolHeaderWrite', + ProtocolMessageWrite = 'protocolMessageWrite', + ProtocolWrite = 'protocolWrite', +} + +export namespace SocketDiagnostics { + + export const enableDiagnostics = false; + + export interface IRecord { + timestamp: number; + id: string; + label: string; + type: SocketDiagnosticsEventType; + buff?: VSBuffer; + data?: any; + } + + export const records: IRecord[] = []; + const socketIds = new WeakMap(); + let lastUsedSocketId = 0; + + function getSocketId(nativeObject: any, label: string): string { + if (!socketIds.has(nativeObject)) { + const id = String(++lastUsedSocketId); + socketIds.set(nativeObject, id); + } + return socketIds.get(nativeObject)!; + } + + export function traceSocketEvent(nativeObject: any, socketDebugLabel: string, type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + if (!enableDiagnostics) { + return; + } + const id = getSocketId(nativeObject, socketDebugLabel); + + if (data instanceof VSBuffer || data instanceof Uint8Array || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { + const copiedData = VSBuffer.alloc(data.byteLength); + copiedData.set(data); + records.push({ timestamp: Date.now(), id, label: socketDebugLabel, type, buff: copiedData }); + } else { + // data is a custom object + records.push({ timestamp: Date.now(), id, label: socketDebugLabel, type, data: data }); + } + } +} + export const enum SocketCloseEventType { NodeSocketCloseEvent = 0, WebSocketCloseEvent = 1 @@ -60,6 +143,8 @@ export interface ISocket extends IDisposable { write(buffer: VSBuffer): void; end(): void; drain(): Promise; + + traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void; } let emptyBuffer: VSBuffer | null = null; @@ -173,6 +258,18 @@ const enum ProtocolMessageType { ReplayRequest = 6 } +function protocolMessageTypeToString(messageType: ProtocolMessageType) { + switch (messageType) { + case ProtocolMessageType.None: return 'None'; + case ProtocolMessageType.Regular: return 'Regular'; + case ProtocolMessageType.Control: return 'Control'; + case ProtocolMessageType.Ack: return 'Ack'; + case ProtocolMessageType.KeepAlive: return 'KeepAlive'; + case ProtocolMessageType.Disconnect: return 'Disconnect'; + case ProtocolMessageType.ReplayRequest: return 'ReplayRequest'; + } +} + export const enum ProtocolConstants { HeaderLength = 13, /** @@ -268,6 +365,9 @@ class ProtocolReader extends Disposable { this._state.messageType = buff.readUInt8(0); this._state.id = buff.readUInt32BE(1); this._state.ack = buff.readUInt32BE(5); + + this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolHeaderRead, { messageType: protocolMessageTypeToString(this._state.messageType), id: this._state.id, ack: this._state.ack, messageSize: this._state.readLen }); + } else { // buff is the body const messageType = this._state.messageType; @@ -281,6 +381,8 @@ class ProtocolReader extends Disposable { this._state.id = 0; this._state.ack = 0; + this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolMessageRead, buff); + this._onMessage.fire(new ProtocolMessage(messageType, id, ack, buff)); if (this._isDisposed) { @@ -349,6 +451,10 @@ class ProtocolWriter { header.writeUInt32BE(msg.id, 1); header.writeUInt32BE(msg.ack, 5); header.writeUInt32BE(msg.data.byteLength, 9); + + this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolHeaderWrite, { messageType: protocolMessageTypeToString(msg.type), id: msg.id, ack: msg.ack, messageSize: msg.data.byteLength }); + this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolMessageWrite, msg.data); + this._writeSoon(header, msg.data); } @@ -378,7 +484,9 @@ class ProtocolWriter { if (this._totalLength === 0) { return; } - this._socket.write(this._bufferTake()); + const data = this._bufferTake(); + this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolWrite, { byteLength: data.byteLength }); + this._socket.write(data); } } @@ -983,3 +1091,39 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketWriter.write(msg); } } + +// (() => { +// if (!SocketDiagnostics.enableDiagnostics) { +// return; +// } +// if (typeof require.__$__nodeRequire !== 'function') { +// console.log(`Can only log socket diagnostics on native platforms.`); +// return; +// } +// const type = ( +// process.argv.includes('--type=renderer') +// ? 'renderer' +// : (process.argv.includes('--type=extensionHost') +// ? 'extensionHost' +// : (process.argv.some(item => item.includes('server/main')) +// ? 'server' +// : 'unknown' +// ) +// ) +// ); +// setTimeout(() => { +// SocketDiagnostics.records.forEach(r => { +// if (r.buff) { +// r.data = Buffer.from(r.buff.buffer).toString('base64'); +// r.buff = undefined; +// } +// }); + +// const fs = require.__$__nodeRequire('fs'); +// const path = require.__$__nodeRequire('path'); +// const logPath = path.join(process.cwd(),`${type}-${process.pid}`); + +// console.log(`dumping socket diagnostics at ${logPath}`); +// fs.writeFileSync(logPath, JSON.stringify(SocketDiagnostics.records)); +// }, 20000); +// })(); diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index f4f55e8eea825..32c264283580e 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -14,17 +14,25 @@ import { join } from 'vs/base/common/path'; import { Platform, platform } from 'vs/base/common/platform'; import { generateUuid } from 'vs/base/common/uuid'; import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc'; -import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; +import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; import * as zlib from 'zlib'; export class NodeSocket implements ISocket { + public readonly debugLabel: string; public readonly socket: Socket; private readonly _errorListener: (err: any) => void; - constructor(socket: Socket) { + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data); + } + + constructor(socket: Socket, debugLabel: string = '') { + this.debugLabel = debugLabel; this.socket = socket; + this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'NodeSocket' }); this._errorListener = (err: any) => { + this.traceSocketEvent(SocketDiagnosticsEventType.Error, { code: err?.code, message: err?.message }); if (err) { if (err.code === 'EPIPE') { // An EPIPE exception at the wrong time can lead to a renderer process crash @@ -47,7 +55,10 @@ export class NodeSocket implements ISocket { } public onData(_listener: (e: VSBuffer) => void): IDisposable { - const listener = (buff: Buffer) => _listener(VSBuffer.wrap(buff)); + const listener = (buff: Buffer) => { + this.traceSocketEvent(SocketDiagnosticsEventType.Read, buff); + _listener(VSBuffer.wrap(buff)); + }; this.socket.on('data', listener); return { dispose: () => this.socket.off('data', listener) @@ -56,6 +67,7 @@ export class NodeSocket implements ISocket { public onClose(listener: (e: SocketCloseEvent) => void): IDisposable { const adapter = (hadError: boolean) => { + this.traceSocketEvent(SocketDiagnosticsEventType.Close, { hadError }); listener({ type: SocketCloseEventType.NodeSocketCloseEvent, hadError: hadError, @@ -69,9 +81,13 @@ export class NodeSocket implements ISocket { } public onEnd(listener: () => void): IDisposable { - this.socket.on('end', listener); + const adapter = () => { + this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndReceived); + listener(); + }; + this.socket.on('end', adapter); return { - dispose: () => this.socket.off('end', listener) + dispose: () => this.socket.off('end', adapter) }; } @@ -87,7 +103,8 @@ export class NodeSocket implements ISocket { // > However, the false return value is only advisory and the writable stream will unconditionally // > accept and buffer chunk even if it has not been allowed to drain. try { - this.socket.write(buffer.buffer, (err: any) => { + this.traceSocketEvent(SocketDiagnosticsEventType.Write, buffer); + this.socket.write(buffer.buffer, (err: any) => { if (err) { if (err.code === 'EPIPE') { // An EPIPE exception at the wrong time can lead to a renderer process crash @@ -116,12 +133,15 @@ export class NodeSocket implements ISocket { } public end(): void { + this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndSent); this.socket.end(); } public drain(): Promise { + this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainBegin); return new Promise((resolve, reject) => { if (this.socket.bufferSize === 0) { + this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainEnd); resolve(); return; } @@ -131,6 +151,7 @@ export class NodeSocket implements ISocket { this.socket.off('error', finished); this.socket.off('timeout', finished); this.socket.off('drain', finished); + this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainEnd); resolve(); }; this.socket.on('close', finished); @@ -209,6 +230,10 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { return VSBuffer.alloc(0); } + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + this.socket.traceSocketEvent(type, data); + } + /** * Create a socket which can communicate using WebSocket frames. * @@ -224,6 +249,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { constructor(socket: NodeSocket, permessageDeflate: boolean, inflateBytes: VSBuffer | null, recordInflateBytes: boolean) { super(); this.socket = socket; + this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'WebSocketNodeSocket', permessageDeflate, inflateBytesLength: inflateBytes?.byteLength || 0, recordInflateBytes }); this._totalIncomingWireBytes = 0; this._totalIncomingDataBytes = 0; this._totalOutgoingWireBytes = 0; @@ -238,6 +264,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { windowBits: 15 }); this._zlibInflate.on('error', (err) => { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err)?.code }); // zlib errors are fatal, since we have no idea how to recover console.error(err); onUnexpectedError(err); @@ -248,11 +275,14 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { }); }); this._zlibInflate.on('data', (data: Buffer) => { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateData, data); this._pendingInflateData.push(data); }); if (inflateBytes) { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWriteInitial, inflateBytes.buffer); this._zlibInflate.write(inflateBytes.buffer); this._zlibInflate.flush(() => { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateInitialFlushFired); this._pendingInflateData.length = 0; }); } @@ -261,6 +291,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { windowBits: 15 }); this._zlibDeflate.on('error', (err) => { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (err)?.code }); // zlib errors are fatal, since we have no idea how to recover console.error(err); onUnexpectedError(err); @@ -271,6 +302,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { }); }); this._zlibDeflate.on('data', (data: Buffer) => { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateData, data); this._pendingDeflateData.push(data); }); } else { @@ -311,11 +343,13 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { this._totalOutgoingDataBytes += buffer.byteLength; if (this._zlibDeflate) { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateWrite, buffer.buffer); this._zlibDeflate.write(buffer.buffer); this._zlibDeflateFlushWaitingCount++; // See https://zlib.net/manual.html#Constants this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateFlushFired); this._zlibDeflateFlushWaitingCount--; let data = Buffer.concat(this._pendingDeflateData); this._pendingDeflateData.length = 0; @@ -338,6 +372,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { } private _write(buffer: VSBuffer, compressed: boolean): void { + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketWrite, buffer); let headerLen = Constants.MinHeaderByteSize; if (buffer.byteLength < 126) { headerLen += 0; @@ -413,6 +448,8 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { this._state.firstFrameOfMessage = Boolean(finBit); this._state.mask = 0; + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { headerSize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin }); + } else if (this._state.state === ReadState.ReadHeader) { // read entire header const header = this._incomingData.read(this._state.readLen); @@ -453,11 +490,16 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { this._state.readLen = len; this._state.mask = mask; + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { bodySize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin, mask: this._state.mask }); + } else if (this._state.state === ReadState.ReadBody) { // read body const body = this._incomingData.read(this._state.readLen); + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketReadData, body); + unmask(body, this._state.mask); + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketUnmaskedData, body); this._state.state = ReadState.PeekHeader; this._state.readLen = Constants.MinHeaderByteSize; @@ -473,14 +515,19 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { if (this._recordInflateBytes) { this._recordedInflateBytes.push(Buffer.from(body.buffer)); } + + this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWrite, body.buffer); this._zlibInflate.write(body.buffer); if (this._state.fin) { if (this._recordInflateBytes) { this._recordedInflateBytes.push(Buffer.from([0x00, 0x00, 0xff, 0xff])); } - this._zlibInflate.write(Buffer.from([0x00, 0x00, 0xff, 0xff])); + const buff = Buffer.from([0x00, 0x00, 0xff, 0xff]); + this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWrite, buff); + this._zlibInflate.write(buff); } this._zlibInflate.flush(() => { + this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateFlushFired); const data = Buffer.concat(this._pendingInflateData); this._pendingInflateData.length = 0; this._totalIncomingDataBytes += data.length; @@ -495,10 +542,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { } public async drain(): Promise { + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainBegin); if (this._zlibDeflateFlushWaitingCount > 0) { await Event.toPromise(this._onDidZlibFlush.event); } await this.socket.drain(); + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainEnd); } } @@ -597,7 +646,7 @@ export class Server extends IPCServer { const onConnection = Event.fromNodeEventEmitter(server, 'connection'); return Event.map(onConnection, socket => ({ - protocol: new Protocol(new NodeSocket(socket)), + protocol: new Protocol(new NodeSocket(socket, 'ipc-server-connection')), onDidClientDisconnect: Event.once(Event.fromNodeEventEmitter(socket, 'close')) })); } @@ -639,7 +688,7 @@ export function connect(hook: any, clientId: string): Promise { return new Promise((c, e) => { const socket = createConnection(hook, () => { socket.removeListener('error', e); - c(Client.fromSocket(new NodeSocket(socket), clientId)); + c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId)); }); socket.once('error', e); diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index d9a5d9b26de02..e81f91b857f4c 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -8,12 +8,12 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { ISocket, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net'; +import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; export interface IWebSocketFactory { - create(url: string): IWebSocket; + create(url: string, debugLabel: string): IWebSocket; } export interface IWebSocketCloseEvent { @@ -41,6 +41,7 @@ export interface IWebSocket { readonly onClose: Event; readonly onError: Event; + traceSocketEvent?(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void; send(data: ArrayBuffer | ArrayBufferView): void; close(): void; } @@ -50,7 +51,8 @@ class BrowserWebSocket extends Disposable implements IWebSocket { private readonly _onData = new Emitter(); public readonly onData = this._onData.event; - public readonly onOpen: Event; + private readonly _onOpen = this._register(new Emitter()); + public readonly onOpen = this._onOpen.event; private readonly _onClose = this._register(new Emitter()); public readonly onClose = this._onClose.event; @@ -58,6 +60,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket { private readonly _onError = this._register(new Emitter()); public readonly onError = this._onError.event; + private readonly _debugLabel: string; private readonly _socket: WebSocket; private readonly _fileReader: FileReader; private readonly _queue: Blob[]; @@ -66,9 +69,15 @@ class BrowserWebSocket extends Disposable implements IWebSocket { private readonly _socketMessageListener: (ev: MessageEvent) => void; - constructor(socket: WebSocket) { + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + SocketDiagnostics.traceSocketEvent(this._socket, this._debugLabel, type, data); + } + + constructor(url: string, debugLabel: string) { super(); - this._socket = socket; + this._debugLabel = debugLabel; + this._socket = new WebSocket(url); + this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'BrowserWebSocket', url }); this._fileReader = new FileReader(); this._queue = []; this._isReading = false; @@ -78,6 +87,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket { this._isReading = false; const buff = (event.target).result; + this.traceSocketEvent(SocketDiagnosticsEventType.Read, buff); this._onData.fire(buff); if (this._queue.length > 0) { @@ -95,11 +105,16 @@ class BrowserWebSocket extends Disposable implements IWebSocket { }; this._socketMessageListener = (ev: MessageEvent) => { - enqueue(ev.data); + const blob = (ev.data); + this.traceSocketEvent(SocketDiagnosticsEventType.BrowserWebSocketBlobReceived, { type: blob.type, size: blob.size }); + enqueue(blob); }; this._socket.addEventListener('message', this._socketMessageListener); - this.onOpen = Event.fromDOMEventEmitter(this._socket, 'open'); + this._register(dom.addDisposableListener(this._socket, 'open', (e) => { + this.traceSocketEvent(SocketDiagnosticsEventType.Open); + this._onOpen.fire(); + })); // WebSockets emit error events that do not contain any real information // Our only chance of getting to the root cause of an error is to @@ -134,6 +149,8 @@ class BrowserWebSocket extends Disposable implements IWebSocket { }; this._register(dom.addDisposableListener(this._socket, 'close', (e: CloseEvent) => { + this.traceSocketEvent(SocketDiagnosticsEventType.Close, { code: e.code, reason: e.reason, wasClean: e.wasClean }); + this._isClosed = true; if (pendingErrorEvent) { @@ -157,7 +174,10 @@ class BrowserWebSocket extends Disposable implements IWebSocket { this._onClose.fire({ code: e.code, reason: e.reason, wasClean: e.wasClean, event: e }); })); - this._register(dom.addDisposableListener(this._socket, 'error', sendErrorSoon)); + this._register(dom.addDisposableListener(this._socket, 'error', (err) => { + this.traceSocketEvent(SocketDiagnosticsEventType.Error, { message: err?.message }); + sendErrorSoon(err); + })); } send(data: ArrayBuffer | ArrayBufferView): void { @@ -165,11 +185,13 @@ class BrowserWebSocket extends Disposable implements IWebSocket { // Refuse to write data to closed WebSocket... return; } + this.traceSocketEvent(SocketDiagnosticsEventType.Write, data); this._socket.send(data); } close(): void { this._isClosed = true; + this.traceSocketEvent(SocketDiagnosticsEventType.Close); this._socket.close(); this._socket.removeEventListener('message', this._socketMessageListener); this.dispose(); @@ -177,16 +199,27 @@ class BrowserWebSocket extends Disposable implements IWebSocket { } export const defaultWebSocketFactory = new class implements IWebSocketFactory { - create(url: string): IWebSocket { - return new BrowserWebSocket(new WebSocket(url)); + create(url: string, debugLabel: string): IWebSocket { + return new BrowserWebSocket(url, debugLabel); } }; class BrowserSocket implements ISocket { + public readonly socket: IWebSocket; + public readonly debugLabel: string; + + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + if (typeof this.socket.traceSocketEvent === 'function') { + this.socket.traceSocketEvent(type, data); + } else { + SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data); + } + } - constructor(socket: IWebSocket) { + constructor(socket: IWebSocket, debugLabel: string) { this.socket = socket; + this.debugLabel = debugLabel; } public dispose(): void { @@ -239,13 +272,13 @@ export class BrowserSocketFactory implements ISocketFactory { this._webSocketFactory = webSocketFactory || defaultWebSocketFactory; } - connect(host: string, port: number, query: string, callback: IConnectCallback): void { + connect(host: string, port: number, query: string, debugLabel: string, callback: IConnectCallback): void { const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws'); - const socket = this._webSocketFactory.create(`${webSocketSchema}://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`); + const socket = this._webSocketFactory.create(`${webSocketSchema}://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`, debugLabel); const errorListener = socket.onError((err) => callback(err, undefined)); socket.onOpen(() => { errorListener.dispose(); - callback(undefined, new BrowserSocket(socket)); + callback(undefined, new BrowserSocket(socket, debugLabel)); }); } } diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 3af66e0a9bd49..b555abd36942a 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -85,7 +85,7 @@ export interface IConnectCallback { } export interface ISocketFactory { - connect(host: string, port: number, query: string, callback: IConnectCallback): void; + connect(host: string, port: number, query: string, debugLabel: string, callback: IConnectCallback): void; } function createTimeoutCancellation(millis: number): CancellationToken { @@ -188,9 +188,9 @@ function readOneControlMessage(protocol: PersistentProtocol, timeoutCancellat return result.promise; } -function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, query: string, timeoutCancellationToken: CancellationToken): Promise { +function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, query: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise { const result = new PromiseWithTimeout(timeoutCancellationToken); - socketFactory.connect(host, port, query, (err: any, socket: ISocket | undefined) => { + socketFactory.connect(host, port, query, debugLabel, (err: any, socket: ISocket | undefined) => { if (result.didTimeout) { if (err) { logService.error(err); @@ -231,7 +231,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio let socket: ISocket; try { - socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, timeoutCancellationToken); + socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken); } catch (error) { options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`); options.logService.error(error); diff --git a/src/vs/platform/remote/node/nodeSocketFactory.ts b/src/vs/platform/remote/node/nodeSocketFactory.ts index a0359ed76f66c..4f5a2c938a130 100644 --- a/src/vs/platform/remote/node/nodeSocketFactory.ts +++ b/src/vs/platform/remote/node/nodeSocketFactory.ts @@ -8,7 +8,7 @@ import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; export const nodeSocketFactory = new class implements ISocketFactory { - connect(host: string, port: number, query: string, callback: IConnectCallback): void { + connect(host: string, port: number, query: string, debugLabel: string, callback: IConnectCallback): void { const errorListener = (err: any) => callback(err, undefined); const socket = net.createConnection({ host: host, port: port }, () => { @@ -34,7 +34,7 @@ export const nodeSocketFactory = new class implements ISocketFactory { if (strData.indexOf('\r\n\r\n') >= 0) { // headers received OK socket.off('data', onData); - callback(undefined, new NodeSocket(socket)); + callback(undefined, new NodeSocket(socket, debugLabel)); } }; socket.on('data', onData); diff --git a/src/vs/server/remoteExtensionHostAgentServer.ts b/src/vs/server/remoteExtensionHostAgentServer.ts index 60f925fe19119..b3a66234045ba 100644 --- a/src/vs/server/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/remoteExtensionHostAgentServer.ts @@ -497,9 +497,9 @@ export class RemoteExtensionHostAgentServer extends Disposable { // Finally! if (skipWebSocketFrames) { - this._handleWebSocketConnection(new NodeSocket(socket), isReconnection, reconnectionToken); + this._handleWebSocketConnection(new NodeSocket(socket, `server-connection-${reconnectionToken}`), isReconnection, reconnectionToken); } else { - this._handleWebSocketConnection(new WebSocketNodeSocket(new NodeSocket(socket), permessageDeflate, null, true), isReconnection, reconnectionToken); + this._handleWebSocketConnection(new WebSocketNodeSocket(new NodeSocket(socket, `server-connection-${reconnectionToken}`), permessageDeflate, null, true), isReconnection, reconnectionToken); } } diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 7c141c4a42de4..35dae9ea87640 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -450,7 +450,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { // using a buffered message protocol here because between now // and the first time a `then` executes some messages might be lost // unless we immediately register a listener for `onMessage`. - resolve(new PersistentProtocol(new NodeSocket(this._extensionHostConnection))); + resolve(new PersistentProtocol(new NodeSocket(this._extensionHostConnection, 'renderer-exthost'))); }); // Now that the named pipe listener is installed, start the ext host process diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 70b86fcd4d6e0..36dc1a6416640 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -123,10 +123,10 @@ function _createExtHostProtocol(): Promise { const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64')); let socket: NodeSocket | WebSocketNodeSocket; if (msg.skipWebSocketFrames) { - socket = new NodeSocket(handle); + socket = new NodeSocket(handle, 'extHost-socket'); } else { const inflateBytes = VSBuffer.wrap(Buffer.from(msg.inflateBytes, 'base64')); - socket = new WebSocketNodeSocket(new NodeSocket(handle), msg.permessageDeflate, inflateBytes, false); + socket = new WebSocketNodeSocket(new NodeSocket(handle, 'extHost-socket'), msg.permessageDeflate, inflateBytes, false); } if (protocol) { // reconnection case @@ -174,7 +174,7 @@ function _createExtHostProtocol(): Promise { const socket = net.createConnection(pipeName, () => { socket.removeListener('error', reject); - resolve(new PersistentProtocol(new NodeSocket(socket))); + resolve(new PersistentProtocol(new NodeSocket(socket, 'extHost-renderer'))); }); socket.once('error', reject); From f04acdb07e076527152347a2e0ab0b9036d689e6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 25 Nov 2021 09:52:04 +0100 Subject: [PATCH 0033/2210] #15756 fine tune labels --- .../browser/extensions.contribution.ts | 4 ++-- .../extensions/browser/extensionsActions.ts | 16 ++++++++-------- .../extensions/browser/extensionsWidgets.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 01caee7a0a939..00c9e0370180e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1158,7 +1158,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi private registerContextMenuActions(): void { this.registerExtensionAction({ id: 'workbench.extensions.action.showPreReleaseVersion', - title: { value: localize('show pre-release version', "Show Pre-release Version"), original: 'Show Pre-release Version' }, + title: { value: localize('show pre-release version', "Show Pre-Release Version"), original: 'Show Pre-Release Version' }, menu: { id: MenuId.ExtensionContext, group: '0_install', @@ -1173,7 +1173,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }); this.registerExtensionAction({ id: 'workbench.extensions.action.showReleasedVersion', - title: { value: localize('show released version', "Show Released Version"), original: 'Show Released Version' }, + title: { value: localize('show released version', "Show Release Version"), original: 'Show Release Version' }, menu: { id: MenuId.ExtensionContext, group: '0_install', diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index f6025ecaea7f4..940748768725d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -148,9 +148,9 @@ export class PromptExtensionInstallFailureAction extends Action { if (ExtensionManagementErrorCode.IncompatiblePreRelease === (this.error.name)) { operationMessage = getErrorMessage(this.error); - additionalMessage = localize('install release version', "Would you like to install the released version?"); + additionalMessage = localize('install release version message', "Would you like to install the release version?"); promptChoices.push({ - label: localize('install released version', "Install Released Version"), + label: localize('install release version', "Install Release Version"), run: () => { const installAction = this.installOptions?.isMachineScoped ? this.instantiationService.createInstance(InstallAction, !!this.installOptions.installPreReleaseVersion) : this.instantiationService.createInstance(InstallAndSyncAction, !!this.installOptions?.installPreReleaseVersion); installAction.extension = this.extension; @@ -367,11 +367,11 @@ export abstract class AbstractInstallAction extends ExtensionAction { getLabel(primary?: boolean): string { /* install pre-release version */ if (this.installPreReleaseVersion && this.extension?.hasPreReleaseVersion) { - return primary ? localize('install pre-release', "Install Pre-release") : localize('install pre-release version', "Install Pre-release Version"); + return primary ? localize('install pre-release', "Install Pre-Release") : localize('install pre-release version', "Install Pre-Release Version"); } /* install released version that has a pre release version */ if (this.extension?.hasPreReleaseVersion) { - return primary ? localize('install', "Install") : localize('install released version', "Install Released Version"); + return primary ? localize('install', "Install") : localize('install release version', "Install Release Version"); } return localize('install', "Install"); } @@ -1076,7 +1076,7 @@ export class MenuItemExtensionAction extends ExtensionAction { export class SwitchToPreReleaseVersionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.switchToPreReleaseVersion'; - static readonly TITLE = { value: localize('switch to pre-release version', "Switch to Pre-release Version"), original: 'Switch to Pre-release Version' }; + static readonly TITLE = { value: localize('switch to pre-release version', "Switch to Pre-Release Version"), original: 'Switch to Pre-Release Version' }; private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled`; @@ -1101,8 +1101,8 @@ export class SwitchToPreReleaseVersionAction extends ExtensionAction { export class SwitchToReleasedVersionAction extends ExtensionAction { - static readonly ID = 'workbench.extensions.action.switchToReleasedVersion'; - static readonly TITLE = { value: localize('switch to released version', "Switch to Released Version"), original: 'Switch to Released Version' }; + static readonly ID = 'workbench.extensions.action.switchToReleaseVersion'; + static readonly TITLE = { value: localize('switch to release version', "Switch to Release Version"), original: 'Switch to Release Version' }; private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled`; @@ -1175,7 +1175,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension!.version ? ` (${localize('current', "current")})` : ''}`, latest: i === 0, - ariaLabel: `${v.isPreReleaseVersion ? 'Pre-release version' : 'Released version'} ${v.version}`, + ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`, isPreReleaseVersion: v.isPreReleaseVersion }; }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 4d365226d64f4..6746a8b97b4d2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -184,7 +184,7 @@ export class PreReleaseIndicatorWidget extends ExtensionWidget { append(this.container, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); } if (this.options?.label) { - append(this.container, $('span.pre-releaselabel', undefined, localize('pre-release-label', "Pre-release"))); + append(this.container, $('span.pre-releaselabel', undefined, localize('pre-release-label', "Pre-Release"))); } } } @@ -583,7 +583,7 @@ export class ExtensionHoverWidget extends ExtensionWidget { return undefined; } const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor); - const preReleaseVersionLink = `[${localize('Show prerelease version', "Pre-release version")}](${URI.parse(`command:workbench.extensions.action.showPreReleaseVersion?${encodeURIComponent(JSON.stringify([extension.identifier.id]))}`)})`; + const preReleaseVersionLink = `[${localize('Show prerelease version', "Pre-Release version")}](${URI.parse(`command:workbench.extensions.action.showPreReleaseVersion?${encodeURIComponent(JSON.stringify([extension.identifier.id]))}`)})`; const message = localize('has prerelease', "This extension has a {0} available", preReleaseVersionLink); return `$(${preReleaseIcon.id}) ${message}`; } From 7ca8fbe2d38dfbffcccd58f55c38a7e6e43a8738 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 25 Nov 2021 09:45:26 +0100 Subject: [PATCH 0034/2210] icon themes: fix duplicated marketplace item --- .../workbench/contrib/themes/browser/themes.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 1c26893ecd1f3..2119f146186fa 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -358,7 +358,7 @@ abstract class AbstractIconThemeAction extends Action { protected abstract setTheme(id: string, settingsTarget: ThemeSettingTarget): Promise; protected pick(themes: IWorkbenchTheme[], currentTheme: IWorkbenchTheme) { - let picks: QuickPickInput[] = [this.builtInEntry, ...toEntries(themes), ...configurationEntries(this.installMessage)]; + let picks: QuickPickInput[] = [this.builtInEntry, ...toEntries(themes), ...(this.extensionGalleryService.isEnabled() ? configurationEntries(this.installMessage) : [])]; let selectThemeTimeout: number | undefined; @@ -383,7 +383,7 @@ abstract class AbstractIconThemeAction extends Action { const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === currentTheme.id); const quickpick = this.quickInputService.createQuickPick(); - quickpick.items = this.extensionGalleryService.isEnabled() ? picks.concat(configurationEntries(this.installMessage)) : picks; + quickpick.items = picks; quickpick.placeholder = this.placeholderMessage; quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; quickpick.canSelectMany = false; From 228ac5b3c1a698e5b46e4b75abb162c6568334a9 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 10:25:00 +0100 Subject: [PATCH 0035/2210] Add `traceSocketEvent` for fake socket --- src/vs/base/parts/ipc/test/node/ipc.net.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index ae27949be6855..4345fcccaca5a 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -11,7 +11,7 @@ import { Barrier, timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent } from 'vs/base/parts/ipc/common/ipc.net'; +import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -432,6 +432,9 @@ suite('WebSocketNodeSocket', () => { private readonly _onClose = new Emitter(); public readonly onClose = this._onClose.event; + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + } + constructor() { super(); } From 131f9fa97c5990bbb994cc3ec182640daf44244e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 25 Nov 2021 11:03:50 +0100 Subject: [PATCH 0036/2210] #46851 fix init --- .../configuration/browser/configuration.ts | 1 + .../test/browser/configuration.test.ts | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 5a7d8566db498..328689e59998e 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -57,6 +57,7 @@ export class DefaultConfiguration extends Disposable { async initialize(): Promise { await this.initializeCachedConfigurationDefaultsOverrides(); + this._configurationModel = undefined; this._register(this.configurationRegistry.onDidUpdateConfiguration(({ defaultsOverrides }) => this.onDidUpdateConfiguration(defaultsOverrides))); return this.configurationModel; } diff --git a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts index 4b7465b41dee1..8ab651ca17e82 100644 --- a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts @@ -62,6 +62,27 @@ suite('DefaultConfiguration', () => { assert.deepStrictEqual(actual.getValue('test.configurationDefaultsOverride'), 'overrideValue'); }); + test('configuration default overrides are read from cache when model is read before initialize', async () => { + await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); + const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + + assert.deepStrictEqual(testObject.configurationModel.getValue('test.configurationDefaultsOverride'), 'defaultValue'); + + const actual = await testObject.initialize(); + + assert.deepStrictEqual(actual.getValue('test.configurationDefaultsOverride'), 'overrideValue'); + assert.deepStrictEqual(testObject.configurationModel.getValue('test.configurationDefaultsOverride'), 'overrideValue'); + }); + + test('configuration default overrides are read from cache', async () => { + await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); + const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + + const actual = await testObject.initialize(); + + assert.deepStrictEqual(actual.getValue('test.configurationDefaultsOverride'), 'overrideValue'); + }); + test('configuration default overrides read from cache override environment', async () => { const environmentService = new BrowserWorkbenchEnvironmentService({ logsPath: joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'logs'), workspaceId: '', configurationDefaults: { 'test.configurationDefaultsOverride': 'envOverrideValue' } }, TestProductService); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); From 5e5bb86a25dffdcc8805c5dfd803ce1e556438b3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 25 Nov 2021 11:37:31 +0100 Subject: [PATCH 0037/2210] take extension id while registering defaults --- .../common/configurationRegistry.ts | 25 +++++++++++-------- .../test/common/configurationRegistry.test.ts | 12 ++++----- .../api/browser/mainThreadTunnelService.ts | 4 +-- .../api/common/configurationExtensionPoint.ts | 14 +++++------ .../contrib/remote/browser/remoteExplorer.ts | 2 +- .../configuration/browser/configuration.ts | 2 +- .../test/browser/configuration.test.ts | 4 +-- .../test/browser/configurationService.test.ts | 6 +++-- 8 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 6538dbdf2cf48..c0c6d51a5b69e 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -48,12 +48,12 @@ export interface IConfigurationRegistry { /** * Register multiple default configurations to the registry. */ - registerDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void; + registerDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void; /** * Deregister multiple default configurations from the registry. */ - deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void; + deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void; /** * Return the registered configuration defaults overrides @@ -182,6 +182,11 @@ export interface IConfigurationNode { extensionInfo?: IConfigurationExtensionInfo; } +export interface IConfigurationDefaults { + overrides: IStringDictionary; + extensionId?: string; +} + export const allSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const applicationSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const machineSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; @@ -255,16 +260,16 @@ class ConfigurationRegistry implements IConfigurationRegistry { this._onDidUpdateConfiguration.fire({ properties: distinct(properties) }); } - public registerDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void { + public registerDefaultConfigurations(configurationDefaults: IConfigurationDefaults[]): void { const properties: string[] = []; const overrideIdentifiers: string[] = []; - for (const defaultConfiguration of defaultConfigurations) { - for (const key in defaultConfiguration) { + for (const { overrides } of configurationDefaults) { + for (const key in overrides) { properties.push(key); if (OVERRIDE_PROPERTY_REGEX.test(key)) { - this.configurationDefaultsOverrides[key] = { ...(this.configurationDefaultsOverrides[key] || {}), ...defaultConfiguration[key] }; + this.configurationDefaultsOverrides[key] = { ...(this.configurationDefaultsOverrides[key] || {}), ...overrides[key] }; const property: IConfigurationPropertySchema = { type: 'object', default: this.configurationDefaultsOverrides[key], @@ -275,7 +280,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.configurationProperties[key] = property; this.defaultLanguageConfigurationOverridesNode.properties![key] = property; } else { - this.configurationDefaultsOverrides[key] = defaultConfiguration[key]; + this.configurationDefaultsOverrides[key] = overrides[key]; const property = this.configurationProperties[key]; if (property) { this.updatePropertyDefaultValue(key, property); @@ -290,10 +295,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true }); } - public deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void { + public deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void { const properties: string[] = []; - for (const defaultConfiguration of defaultConfigurations) { - for (const key in defaultConfiguration) { + for (const { overrides } of defaultConfigurations) { + for (const key in overrides) { properties.push(key); delete this.configurationDefaultsOverrides[key]; if (OVERRIDE_PROPERTY_REGEX.test(key)) { diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 03082cc0d2bac..83314c83ad361 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -21,16 +21,16 @@ suite('ConfigurationRegistry', () => { } } }); - configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 1, b: 2 } }]); - configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 2, c: 3 } }]); + configurationRegistry.registerDefaultConfigurations([{ overrides: { 'config': { a: 1, b: 2 } } }]); + configurationRegistry.registerDefaultConfigurations([{ overrides: { '[lang]': { a: 2, c: 3 } } }]); assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 1, b: 2 }); assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, c: 3 }); }); test('configuration override defaults - merges defaults', async () => { - configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 1, b: 2 } }]); - configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 2, c: 3 } }]); + configurationRegistry.registerDefaultConfigurations([{ overrides: { '[lang]': { a: 1, b: 2 } } }]); + configurationRegistry.registerDefaultConfigurations([{ overrides: { '[lang]': { a: 2, c: 3 } } }]); assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, b: 2, c: 3 }); }); @@ -45,8 +45,8 @@ suite('ConfigurationRegistry', () => { } } }); - configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 1, b: 2 } }]); - configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 2, c: 3 } }]); + configurationRegistry.registerDefaultConfigurations([{ overrides: { 'config': { a: 1, b: 2 } } }]); + configurationRegistry.registerDefaultConfigurations([{ overrides: { 'config': { a: 2, c: 3 } } }]); assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, c: 3 }); }); diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index a1d54ed6423b9..025f8f523e0af 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -204,12 +204,12 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun switch (source) { case CandidatePortSource.None: { Registry.as(ConfigurationExtensions.Configuration) - .registerDefaultConfigurations([{ 'remote.autoForwardPorts': false }]); + .registerDefaultConfigurations([{ overrides: { 'remote.autoForwardPorts': false } }]); break; } case CandidatePortSource.Output: { Registry.as(ConfigurationExtensions.Configuration) - .registerDefaultConfigurations([{ 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT }]); + .registerDefaultConfigurations([{ overrides: { 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT } }]); break; } default: // Do nothing, the defaults for these settings should be used. diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 77824674812df..d59ced8280dd2 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX, windowSettings, resourceSettings, machineOverridableSettings } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX, windowSettings, resourceSettings, machineOverridableSettings, IConfigurationDefaults } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject } from 'vs/base/common/types'; @@ -144,24 +144,24 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { if (removed.length) { - const removedDefaultConfigurations = removed.map>(extension => objects.deepClone(extension.value)); + const removedDefaultConfigurations = removed.map(extension => ({ overrides: objects.deepClone(extension.value), extensionId: extension.description.identifier.value })); configurationRegistry.deregisterDefaultConfigurations(removedDefaultConfigurations); } if (added.length) { const registeredProperties = configurationRegistry.getConfigurationProperties(); const allowedScopes = [ConfigurationScope.MACHINE_OVERRIDABLE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]; - const addedDefaultConfigurations = added.map>(extension => { - const defaults: IStringDictionary = objects.deepClone(extension.value); - for (const key of Object.keys(defaults)) { + const addedDefaultConfigurations = added.map(extension => { + const overrides: IStringDictionary = objects.deepClone(extension.value); + for (const key of Object.keys(overrides)) { if (!OVERRIDE_PROPERTY_REGEX.test(key)) { const registeredPropertyScheme = registeredProperties[key]; if (registeredPropertyScheme.scope && !allowedScopes.includes(registeredPropertyScheme.scope)) { extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for machine-overridable, window, resource and language overridable scoped settings are supported.", key)); - delete defaults[key]; + delete overrides[key]; } } } - return defaults; + return { overrides, extensionId: extension.description.identifier.value }; }); configurationRegistry.registerDefaultConfigurations(addedDefaultConfigurations); } diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index fa8371041cfcd..c28ed8eea3b2c 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -200,7 +200,7 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon remoteAgentService.getEnvironment().then(environment => { if (environment?.os !== OperatingSystem.Linux) { Registry.as(ConfigurationExtensions.Configuration) - .registerDefaultConfigurations([{ 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT }]); + .registerDefaultConfigurations([{ overrides: { 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT } }]); this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, externalOpenerService, remoteExplorerService, configurationService, debugService, tunnelService, remoteAgentService, hostService, logService, () => false)); } else { diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 328689e59998e..d9bd5b473beaa 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -43,7 +43,7 @@ export class DefaultConfiguration extends Disposable { ) { super(); if (environmentService.options?.configurationDefaults) { - this.configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]); + this.configurationRegistry.registerDefaultConfigurations([{ overrides: environmentService.options.configurationDefaults }]); } } diff --git a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts index 8ab651ca17e82..29fa4cfa729e8 100644 --- a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts @@ -44,7 +44,7 @@ suite('DefaultConfiguration', () => { teardown(() => { configurationRegistry.deregisterConfigurations(configurationRegistry.getConfigurations()); - configurationRegistry.deregisterDefaultConfigurations([configurationRegistry.getConfigurationDefaultsOverrides()]); + configurationRegistry.deregisterDefaultConfigurations([{ overrides: configurationRegistry.getConfigurationDefaultsOverrides() }]); }); test('configuration default overrides are read from environment', async () => { @@ -138,7 +138,7 @@ suite('DefaultConfiguration', () => { const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - configurationRegistry.registerDefaultConfigurations([{ 'test.configurationDefaultsOverride': 'newoverrideValue' }]); + configurationRegistry.registerDefaultConfigurations([{ overrides: { 'test.configurationDefaultsOverride': 'newoverrideValue' } }]); await promise; const actual = JSON.parse(await configurationCache.read(cacheKey)); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 495086226d028..ef8885db07110 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -672,8 +672,10 @@ suite('WorkspaceConfigurationService - Folder', () => { }); configurationRegistry.registerDefaultConfigurations([{ - '[jsonc]': { - 'configurationService.folder.languageSetting': 'languageValue' + overrides: { + '[jsonc]': { + 'configurationService.folder.languageSetting': 'languageValue' + } } }]); }); From babe6a6a94b81821685403404251fef93d951fb9 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 11:47:55 +0100 Subject: [PATCH 0038/2210] Extract `ZlibInflateStream` --- src/vs/base/common/buffer.ts | 14 +++ src/vs/base/parts/ipc/common/ipc.net.ts | 2 +- src/vs/base/parts/ipc/node/ipc.net.ts | 127 ++++++++++++++++-------- 3 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 0e28b41904b60..456497130904b 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -43,6 +43,14 @@ export class VSBuffer { } } + static fromByteArray(source: number[]): VSBuffer { + const result = VSBuffer.alloc(source.length); + for (let i = 0, len = source.length; i < len; i++) { + result.buffer[i] = source[i]; + } + return result; + } + static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer { if (typeof totalLength === 'undefined') { totalLength = 0; @@ -70,6 +78,12 @@ export class VSBuffer { this.byteLength = this.buffer.byteLength; } + clone(): VSBuffer { + const result = VSBuffer.alloc(this.byteLength); + result.set(this); + return result; + } + toString(): string { if (hasBuffer) { return this.buffer.toString(); diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 5075a80ec4fff..5ff8d7bcf4025 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -25,7 +25,7 @@ export const enum SocketDiagnosticsEventType { zlibInflateError = 'zlibInflateError', zlibInflateData = 'zlibInflateData', - zlibInflateWriteInitial = 'zlibInflateWriteInitial', + zlibInflateInitialWrite = 'zlibInflateInitialWrite', zlibInflateInitialFlushFired = 'zlibInflateInitialFlushFired', zlibInflateWrite = 'zlibInflateWrite', zlibInflateFlushFired = 'zlibInflateFlushFired', diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 32c264283580e..d766f3f125c0d 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -174,10 +174,14 @@ const enum ReadState { Fin = 4 } +interface ISocketTracer { + traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void +} + /** * See https://tools.ietf.org/html/rfc6455#section-5.2 */ -export class WebSocketNodeSocket extends Disposable implements ISocket { +export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketTracer { public readonly socket: NodeSocket; public readonly permessageDeflate: boolean; @@ -185,13 +189,10 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { private _totalIncomingDataBytes: number; private _totalOutgoingWireBytes: number; private _totalOutgoingDataBytes: number; - private readonly _zlibInflate: zlib.InflateRaw | null; + private readonly _zlibInflateStream: ZlibInflateStream | null; private readonly _zlibDeflate: zlib.DeflateRaw | null; private _zlibDeflateFlushWaitingCount: number; private readonly _onDidZlibFlush = this._register(new Emitter()); - private readonly _recordInflateBytes: boolean; - private readonly _recordedInflateBytes: Buffer[] = []; - private readonly _pendingInflateData: Buffer[] = []; private readonly _pendingDeflateData: Buffer[] = []; private readonly _incomingData: ChunkStream; private readonly _onData = this._register(new Emitter()); @@ -224,8 +225,8 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { } public get recordedInflateBytes(): VSBuffer { - if (this._recordInflateBytes) { - return VSBuffer.wrap(Buffer.concat(this._recordedInflateBytes)); + if (this._zlibInflateStream) { + return this._zlibInflateStream.recordedInflateBytes; } return VSBuffer.alloc(0); } @@ -255,16 +256,14 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { this._totalOutgoingWireBytes = 0; this._totalOutgoingDataBytes = 0; this.permessageDeflate = permessageDeflate; - this._recordInflateBytes = recordInflateBytes; if (permessageDeflate) { // See https://tools.ietf.org/html/rfc7692#page-16 // To simplify our logic, we don't negotiate the window size // and simply dedicate (2^15) / 32kb per web socket - this._zlibInflate = zlib.createInflateRaw({ + this._zlibInflateStream = new ZlibInflateStream(this, recordInflateBytes, inflateBytes, { windowBits: 15 }); - this._zlibInflate.on('error', (err) => { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err)?.code }); + this._register(this._zlibInflateStream.onError((err) => { // zlib errors are fatal, since we have no idea how to recover console.error(err); onUnexpectedError(err); @@ -273,19 +272,11 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { hadError: true, error: err }); - }); - this._zlibInflate.on('data', (data: Buffer) => { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateData, data); - this._pendingInflateData.push(data); - }); - if (inflateBytes) { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWriteInitial, inflateBytes.buffer); - this._zlibInflate.write(inflateBytes.buffer); - this._zlibInflate.flush(() => { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateInitialFlushFired); - this._pendingInflateData.length = 0; - }); - } + })); + this._register(this._zlibInflateStream.onData((data) => { + this._totalIncomingDataBytes += data.byteLength; + this._onData.fire(data); + })); this._zlibDeflate = zlib.createDeflateRaw({ windowBits: 15 @@ -306,7 +297,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { this._pendingDeflateData.push(data); }); } else { - this._zlibInflate = null; + this._zlibInflateStream = null; this._zlibDeflate = null; } this._zlibDeflateFlushWaitingCount = 0; @@ -505,34 +496,19 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { this._state.readLen = Constants.MinHeaderByteSize; this._state.mask = 0; - if (this._zlibInflate && this._state.compressed) { + if (this._zlibInflateStream && this._state.compressed) { // See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2 // Even if permessageDeflate is negotiated, it is possible // that the other side might decide to send uncompressed messages // So only decompress messages that have the RSV 1 bit set // // See https://tools.ietf.org/html/rfc7692#section-7.2.2 - if (this._recordInflateBytes) { - this._recordedInflateBytes.push(Buffer.from(body.buffer)); - } - this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWrite, body.buffer); - this._zlibInflate.write(body.buffer); + this._zlibInflateStream.write(body); if (this._state.fin) { - if (this._recordInflateBytes) { - this._recordedInflateBytes.push(Buffer.from([0x00, 0x00, 0xff, 0xff])); - } - const buff = Buffer.from([0x00, 0x00, 0xff, 0xff]); - this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWrite, buff); - this._zlibInflate.write(buff); + this._zlibInflateStream.write(VSBuffer.fromByteArray([0x00, 0x00, 0xff, 0xff])); } - this._zlibInflate.flush(() => { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateFlushFired); - const data = Buffer.concat(this._pendingInflateData); - this._pendingInflateData.length = 0; - this._totalIncomingDataBytes += data.length; - this._onData.fire(VSBuffer.wrap(data)); - }); + this._zlibInflateStream.flush(); } else { this._totalIncomingDataBytes += body.byteLength; this._onData.fire(body); @@ -551,6 +527,69 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { } } +class ZlibInflateStream extends Disposable { + + private readonly _onError = this._register(new Emitter()); + public readonly onError = this._onError.event; + + private readonly _onData = this._register(new Emitter()); + public readonly onData = this._onData.event; + + private readonly _zlibInflate: zlib.InflateRaw; + private readonly _recordedInflateBytes: VSBuffer[] = []; + private readonly _pendingInflateData: VSBuffer[] = []; + + public get recordedInflateBytes(): VSBuffer { + if (this._recordInflateBytes) { + return VSBuffer.concat(this._recordedInflateBytes); + } + return VSBuffer.alloc(0); + } + + constructor( + private readonly _tracer: ISocketTracer, + private readonly _recordInflateBytes: boolean, + inflateBytes: VSBuffer | null, + options: zlib.ZlibOptions + ) { + super(); + this._zlibInflate = zlib.createInflateRaw(options); + this._zlibInflate.on('error', (err) => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err)?.code }); + this._onError.fire(err); + }); + this._zlibInflate.on('data', (data: Buffer) => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateData, data); + this._pendingInflateData.push(VSBuffer.wrap(data)); + }); + if (inflateBytes) { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateInitialWrite, inflateBytes.buffer); + this._zlibInflate.write(inflateBytes.buffer); + this._zlibInflate.flush(() => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateInitialFlushFired); + this._pendingInflateData.length = 0; + }); + } + } + + public write(buffer: VSBuffer): void { + if (this._recordInflateBytes) { + this._recordedInflateBytes.push(buffer.clone()); + } + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWrite, buffer); + this._zlibInflate.write(buffer.buffer); + } + + public flush(): void { + this._zlibInflate.flush(() => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateFlushFired); + const data = VSBuffer.concat(this._pendingInflateData); + this._pendingInflateData.length = 0; + this._onData.fire(data); + }); + } +} + function unmask(buffer: VSBuffer, mask: number): void { if (mask === 0) { return; From 2f8fb0b32e4382bb3c1944172c974fa2b0000ea0 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 25 Nov 2021 13:53:36 +0100 Subject: [PATCH 0039/2210] workbench.colorCustomizations not working after the latest update. Fixes #137867 --- .../workbench/services/themes/browser/workbenchThemeService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 169e31eacc47d..21e03d8f81ad0 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -453,6 +453,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } try { await themeData.ensureLoaded(this.extensionResourceLoaderService); + themeData.setCustomizations(this.settings); return this.applyTheme(themeData, settingsTarget); } catch (error) { throw new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location?.toString(), error.message)); From 6f2239307b8c9a82fea65624029e1f22902e29fd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 25 Nov 2021 14:37:22 +0100 Subject: [PATCH 0040/2210] Smoke test tweaks (#137809) * smoke - move data migration tests into one and align * fix app starting * `createWorkspaceFile` is not async * :lipstick: * support screenshot on failure even for stable app * smoke - try to remove timeout (#137847) * improve exit call --- test/automation/src/application.ts | 14 +--- test/automation/src/code.ts | 69 +++++++++++------- .../src/areas/multiroot/multiroot.test.ts | 4 +- .../src/areas/workbench/data-loss.test.ts | 39 ----------- .../areas/workbench/data-migration.test.ts | 70 +++++++++++++++++-- test/smoke/src/areas/workbench/launch.test.ts | 21 +++--- .../src/areas/workbench/localization.test.ts | 20 ++++-- test/smoke/src/main.ts | 9 +-- test/smoke/src/utils.ts | 35 +++++----- 9 files changed, 156 insertions(+), 125 deletions(-) delete mode 100644 test/smoke/src/areas/workbench/data-loss.test.ts diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index b4493284a621c..90fbff5a3afcd 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -75,10 +75,9 @@ export class Application { await this.code.waitForElement('.explorer-folders-view'); } - async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise { + async restart(options?: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise { await this.stop(); - await new Promise(c => setTimeout(c, 1000)); - await this._start(options.workspaceOrFolder, options.extraArgs); + await this._start(options?.workspaceOrFolder, options?.extraArgs); } private async _start(workspaceOrFolder = this.workspacePathOrFolder, extraArgs: string[] = []): Promise { @@ -87,15 +86,6 @@ export class Application { await this.checkWindowReady(); } - async reload(): Promise { - this.code.reload() - .catch(err => null); // ignore the connection drop errors - - // needs to be enough to propagate the 'Reload Window' command - await new Promise(c => setTimeout(c, 1500)); - await this.checkWindowReady(); - } - async stop(): Promise { if (this._code) { await this._code.exit(); diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 2b3ebec4c0ef8..a5ab213ff8474 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -290,34 +290,55 @@ export class Code { await this.driver.dispatchKeybinding(windowId, keybinding); } - async reload(): Promise { - const windowId = await this.getActiveWindowId(); - await this.driver.reloadWindow(windowId); - } - async exit(): Promise { - const exitPromise = this.driver.exitApplication(); - - // If we know the `pid`, use that to await the - // process to terminate (desktop). - const pid = this.pid; - if (typeof pid === 'number') { - await (async () => { - while (true) { + return new Promise((resolve, reject) => { + let done = false; + + // Start the exit flow via driver + const exitPromise = this.driver.exitApplication().then(veto => { + if (veto) { + done = true; + reject(new Error('Smoke test exit call resulted in unexpected veto')); + } + }); + + // If we know the `pid` of the smoke tested application + // use that as way to detect the exit of the application + const pid = this.pid; + if (typeof pid === 'number') { + (async () => { + let killCounter = 0; + while (!done) { + killCounter++; + + if (killCounter > 40) { + done = true; + reject(new Error('Smoke test exit call did not terminate main process after 20s, giving up')); + } + + try { + process.kill(pid, 0); // throws an exception if the main process doesn't exist anymore. + await new Promise(resolve => setTimeout(resolve, 500)); + } catch (error) { + done = true; + resolve(); + } + } + })(); + } + + // Otherwise await the exit promise (web). + else { + (async () => { try { - process.kill(pid, 0); // throws an exception if the main process doesn't exist anymore. - await new Promise(c => setTimeout(c, 100)); + await exitPromise; + resolve(); } catch (error) { - return; + reject(new Error(`Smoke test exit call resulted in error: ${error}`)); } - } - })(); - } - - // Otherwise await the exit promise (web). - else { - await exitPromise; - } + })(); + } + }); } async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean, retryCount?: number): Promise { diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index 94350d9a48886..62f3026d5179e 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -17,7 +17,7 @@ function toUri(path: string): string { return `${path}`; } -async function createWorkspaceFile(workspacePath: string): Promise { +function createWorkspaceFile(workspacePath: string): string { const workspaceFilePath = path.join(path.dirname(workspacePath), 'smoketest.code-workspace'); const workspace = { folders: [ @@ -39,7 +39,7 @@ async function createWorkspaceFile(workspacePath: string): Promise { export function setup(opts: minimist.ParsedArgs) { describe('Multiroot', () => { beforeSuite(opts, async opts => { - const workspacePath = await createWorkspaceFile(opts.workspacePath); + const workspacePath = createWorkspaceFile(opts.workspacePath); return { ...opts, workspacePath }; }); diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts deleted file mode 100644 index 4abc0d1c7ecdd..0000000000000 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import minimist = require('minimist'); -import { Application } from '../../../../automation'; -import { afterSuite, beforeSuite } from '../../utils'; - -export function setup(opts: minimist.ParsedArgs) { - - describe('Dataloss', () => { - beforeSuite(opts); - afterSuite(opts); - - it(`verifies that 'hot exit' works for dirty files`, async function () { - const app = this.app as Application; - await app.workbench.editors.newUntitledFile(); - - const untitled = 'Untitled-1'; - const textToTypeInUntitled = 'Hello from Untitled'; - await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); - - const readmeMd = 'readme.md'; - const textToType = 'Hello, Code'; - await app.workbench.quickaccess.openFile(readmeMd); - await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType); - - await app.reload(); - - await app.workbench.editors.waitForActiveTab(readmeMd, true); - await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); - - await app.workbench.editors.waitForTab(untitled); - await app.workbench.editors.selectTab(untitled); - await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); - }); - }); -} diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 11786a1d62a16..def6cc5820b85 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -6,20 +6,78 @@ import { Application, ApplicationOptions, Quality } from '../../../../automation'; import { join } from 'path'; import { ParsedArgs } from 'minimist'; -import { afterSuite, timeout } from '../../utils'; +import { afterSuite, startApp } from '../../utils'; export function setup(opts: ParsedArgs, testDataPath: string) { - describe('Datamigration', () => { + describe('Data Migration (insiders -> insiders)', () => { + + let app: Application | undefined = undefined; + + afterSuite(opts, () => app); + + it(`verifies opened editors are restored`, async function () { + app = await startApp(opts, this.defaultOptions); + + // Open 3 editors and pin 2 of them + await app.workbench.quickaccess.openFile('www'); + await app.workbench.quickaccess.runCommand('View: Keep Editor'); + + await app.workbench.quickaccess.openFile('app.js'); + await app.workbench.quickaccess.runCommand('View: Keep Editor'); + + await app.workbench.editors.newUntitledFile(); + + await app.restart(); + + // Verify 3 editors are open + await app.workbench.editors.selectTab('Untitled-1'); + await app.workbench.editors.selectTab('app.js'); + await app.workbench.editors.selectTab('www'); + + await app.stop(); + app = undefined; + }); + + it(`verifies that 'hot exit' works for dirty files`, async function () { + app = await startApp(opts, this.defaultOptions); + + await app.workbench.editors.newUntitledFile(); + + const untitled = 'Untitled-1'; + const textToTypeInUntitled = 'Hello from Untitled'; + await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); + + const readmeMd = 'readme.md'; + const textToType = 'Hello, Code'; + await app.workbench.quickaccess.openFile(readmeMd); + await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType); + + await app.restart(); + + await app.workbench.editors.waitForTab(readmeMd, true); + await app.workbench.editors.selectTab(readmeMd); + await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); + + await app.workbench.editors.waitForTab(untitled, true); + await app.workbench.editors.selectTab(untitled); + await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); + + await app.stop(); + app = undefined; + }); + }); + + describe('Data Migration (stable -> insiders)', () => { let insidersApp: Application | undefined = undefined; let stableApp: Application | undefined = undefined; - afterSuite(opts, () => insidersApp, async () => stableApp?.stop()); + afterSuite(opts, () => insidersApp ?? stableApp, async () => stableApp?.stop()); it(`verifies opened editors are restored`, async function () { const stableCodePath = opts['stable-build']; - if (!stableCodePath) { + if (!stableCodePath || opts.remote) { this.skip(); } @@ -69,7 +127,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) { it(`verifies that 'hot exit' works for dirty files`, async function () { const stableCodePath = opts['stable-build']; - if (!stableCodePath) { + if (!stableCodePath || opts.remote) { this.skip(); } @@ -94,8 +152,6 @@ export function setup(opts: ParsedArgs, testDataPath: string) { await stableApp.workbench.quickaccess.openFile(readmeMd); await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType); - await timeout(2000); // give time to store the backup before stopping the app - await stableApp.stop(); stableApp = undefined; diff --git a/test/smoke/src/areas/workbench/launch.test.ts b/test/smoke/src/areas/workbench/launch.test.ts index 93074dec346f1..f46a3bdc994b9 100644 --- a/test/smoke/src/areas/workbench/launch.test.ts +++ b/test/smoke/src/areas/workbench/launch.test.ts @@ -4,23 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import minimist = require('minimist'); -import * as path from 'path'; -import { Application, ApplicationOptions } from '../../../../automation'; -import { afterSuite } from '../../utils'; +import { join } from 'path'; +import { Application } from '../../../../automation'; +import { afterSuite, startApp } from '../../utils'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(args: minimist.ParsedArgs) { describe('Launch', () => { - let app: Application; + let app: Application | undefined; - afterSuite(opts, () => app); + afterSuite(args, () => app); it(`verifies that application launches when user data directory has non-ascii characters`, async function () { - const defaultOptions = this.defaultOptions as ApplicationOptions; - const options: ApplicationOptions = { ...defaultOptions, userDataDir: path.join(defaultOptions.userDataDir, 'abcdø') }; - app = new Application(options); - await app.start(); + const massagedOptions = { ...this.defaultOptions, userDataDir: join(this.defaultOptions.userDataDir, 'ø') }; + app = await startApp(args, massagedOptions); + + await app.stop(); + app = undefined; }); }); } diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index 15aeba0ff6bb4..9e27c5ba405fa 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -5,20 +5,23 @@ import minimist = require('minimist'); import { Application, Quality } from '../../../../automation'; -import { afterSuite, beforeSuite } from '../../utils'; +import { afterSuite, startApp } from '../../utils'; + +export function setup(args: minimist.ParsedArgs) { -export function setup(opts: minimist.ParsedArgs) { describe('Localization', () => { - beforeSuite(opts); - afterSuite(opts); - it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () { - const app = this.app as Application; + let app: Application | undefined = undefined; + + afterSuite(args, () => app); - if (app.quality === Quality.Dev || app.remote) { + it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () { + if (this.defaultOptions.quality === Quality.Dev || this.defaultOptions.remote) { return this.skip(); } + app = await startApp(args, this.defaultOptions); + await app.workbench.extensions.openExtensionsViewlet(); await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', false); await app.restart({ extraArgs: ['--locale=DE'] }); @@ -26,6 +29,9 @@ export function setup(opts: minimist.ParsedArgs) { const result = await app.workbench.localization.getLocalizedStrings(); const localeInfo = await app.workbench.localization.getLocaleInfo(); + await app.stop(); + app = undefined; + if (localeInfo.locale === undefined || localeInfo.locale.toLowerCase() !== 'de') { throw new Error(`The requested locale for VS Code was not German. The received value is: ${localeInfo.locale === undefined ? 'not set' : localeInfo.locale}`); } diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index e8690140263d8..05455c15a7830 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -17,7 +17,6 @@ import fetch from 'node-fetch'; import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger } from '../../automation'; import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; -import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; import { setup as setupPreferencesTests } from './areas/preferences/preferences.test'; import { setup as setupSearchTests } from './areas/search/search.test'; import { setup as setupNotebookTests } from './areas/notebook/notebook.test'; @@ -346,14 +345,8 @@ after(async function () { await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c(undefined))); }); -if (!opts.web && opts['build'] && !opts['remote']) { - describe(`Stable vs Insiders Smoke Tests: This test MUST run before releasing`, () => { - setupDataMigrationTests(opts, testDataPath); - }); -} - describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { - if (!opts.web) { setupDataLossTests(opts); } + if (!opts.web) { setupDataMigrationTests(opts, testDataPath); } if (!opts.web) { setupPreferencesTests(opts); } setupSearchTests(opts); setupNotebookTests(opts); diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index 8eb977a3681a8..d4300a74be573 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -19,27 +19,30 @@ export function itRepeat(n: number, description: string, callback: (this: Contex } } -export function beforeSuite(opts: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { +export function beforeSuite(args: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { before(async function () { - let options: ApplicationOptions = { ...this.defaultOptions }; + this.app = await startApp(args, this.defaultOptions, optionsTransform); + }); +} - if (optionsTransform) { - options = await optionsTransform(options); - } +export async function startApp(args: minimist.ParsedArgs, options: ApplicationOptions, optionsTransform?: (opts: ApplicationOptions) => Promise): Promise { + if (optionsTransform) { + options = await optionsTransform({ ...options }); + } - // https://github.com/microsoft/vscode/issues/34988 - const userDataPathSuffix = [...Array(8)].map(() => Math.random().toString(36)[3]).join(''); - const userDataDir = options.userDataDir.concat(`-${userDataPathSuffix}`); + // https://github.com/microsoft/vscode/issues/34988 + const userDataPathSuffix = [...Array(8)].map(() => Math.random().toString(36)[3]).join(''); + const userDataDir = options.userDataDir.concat(`-${userDataPathSuffix}`); - const app = new Application({ ...options, userDataDir }); - await app.start(); - this.app = app; + const app = new Application({ ...options, userDataDir }); + await app.start(); - if (opts.log) { - const title = this.currentTest!.fullTitle(); - app.logger.log('*** Test start:', title); - } - }); + if (args.log) { + const title = this.currentTest!.fullTitle(); + app.logger.log('*** Test start:', title); + } + + return app; } export function afterSuite(opts: minimist.ParsedArgs, appFn?: () => Application | undefined, joinFn?: () => Promise) { From f853123bff8e4c32e014e4fa3568bb049c8a25f5 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:37:19 +0100 Subject: [PATCH 0041/2210] Fix #137870 --- src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 1d04a740c71c7..00f34c3fea90a 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -1162,7 +1162,7 @@ export class DirtyDiffModel extends Disposable { } private diff(): Promise { - return this.progressService.withProgress({ location: ProgressLocation.Scm }, async () => { + return this.progressService.withProgress({ location: ProgressLocation.Scm, delay: 250 }, async () => { return this.getOriginalURIPromise().then(originalURI => { if (this._disposed || this._model.isDisposed() || !originalURI) { return Promise.resolve([]); // disposed From 944d343cc221468a123ff86b5ad14805855a41c0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 25 Nov 2021 14:45:03 +0100 Subject: [PATCH 0042/2210] #46851 add default value source & default default value to configuration properties --- .../common/configurationRegistry.ts | 67 ++++++++++++------- .../configuration/browser/configuration.ts | 6 +- .../test/browser/configuration.test.ts | 3 +- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index c0c6d51a5b69e..bd3b22eca92bc 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -58,7 +58,7 @@ export interface IConfigurationRegistry { /** * Return the registered configuration defaults overrides */ - getConfigurationDefaultsOverrides(): IStringDictionary; + getConfigurationDefaultsOverrides(): Map; /** * Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values. @@ -86,12 +86,12 @@ export interface IConfigurationRegistry { /** * Returns all configurations settings of all configuration nodes contributed to this registry. */ - getConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema }; + getConfigurationProperties(): IStringDictionary; /** * Returns all excluded configurations settings of all configuration nodes contributed to this registry. */ - getExcludedConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema }; + getExcludedConfigurationProperties(): IStringDictionary; /** * Register the identifiers for editor configurations @@ -176,7 +176,7 @@ export interface IConfigurationNode { type?: string | string[]; title?: string; description?: string; - properties?: { [path: string]: IConfigurationPropertySchema; }; + properties?: IStringDictionary; allOf?: IConfigurationNode[]; scope?: ConfigurationScope; extensionInfo?: IConfigurationExtensionInfo; @@ -187,6 +187,14 @@ export interface IConfigurationDefaults { extensionId?: string; } +export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & { + defaultDefaultValue?: any, + source?: string, + defaultSource?: string +}; + +export type IConfigurationDefaultOverride = { value: any, source?: string }; + export const allSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const applicationSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const machineSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; @@ -200,11 +208,11 @@ const contributionRegistry = Registry.as(JSONExtensio class ConfigurationRegistry implements IConfigurationRegistry { - private readonly configurationDefaultsOverrides: IStringDictionary; + private readonly configurationDefaultsOverrides: Map; private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode; private readonly configurationContributors: IConfigurationNode[]; - private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema }; - private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema }; + private readonly configurationProperties: IStringDictionary; + private readonly excludedConfigurationProperties: IStringDictionary; private readonly resourceLanguageSettingsSchema: IJSONSchema; private readonly overrideIdentifiers = new Set(); @@ -215,7 +223,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event; constructor() { - this.configurationDefaultsOverrides = {}; + this.configurationDefaultsOverrides = new Map(); this.defaultLanguageConfigurationOverridesNode = { id: 'defaultOverrides', title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"), @@ -264,23 +272,26 @@ class ConfigurationRegistry implements IConfigurationRegistry { const properties: string[] = []; const overrideIdentifiers: string[] = []; - for (const { overrides } of configurationDefaults) { + for (const { overrides, extensionId } of configurationDefaults) { for (const key in overrides) { properties.push(key); if (OVERRIDE_PROPERTY_REGEX.test(key)) { - this.configurationDefaultsOverrides[key] = { ...(this.configurationDefaultsOverrides[key] || {}), ...overrides[key] }; - const property: IConfigurationPropertySchema = { + const defaultValue = { ...(this.configurationDefaultsOverrides.get(key)?.value || {}), ...overrides[key] }; + this.configurationDefaultsOverrides.set(key, { source: extensionId, value: defaultValue }); + const property: IRegisteredConfigurationPropertySchema = { type: 'object', - default: this.configurationDefaultsOverrides[key], + default: defaultValue, description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key), - $ref: resourceLanguageSettingsSchemaId + $ref: resourceLanguageSettingsSchemaId, + defaultDefaultValue: defaultValue, + source: extensionId, }; overrideIdentifiers.push(...overrideIdentifiersFromKey(key)); this.configurationProperties[key] = property; this.defaultLanguageConfigurationOverridesNode.properties![key] = property; } else { - this.configurationDefaultsOverrides[key] = overrides[key]; + this.configurationDefaultsOverrides.set(key, { value: overrides[key], source: extensionId }); const property = this.configurationProperties[key]; if (property) { this.updatePropertyDefaultValue(key, property); @@ -297,10 +308,13 @@ class ConfigurationRegistry implements IConfigurationRegistry { public deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void { const properties: string[] = []; - for (const { overrides } of defaultConfigurations) { + for (const { overrides, extensionId } of defaultConfigurations) { for (const key in overrides) { + if (this.configurationDefaultsOverrides.get(key)?.source !== extensionId) { + continue; + } properties.push(key); - delete this.configurationDefaultsOverrides[key]; + this.configurationDefaultsOverrides.delete(key); if (OVERRIDE_PROPERTY_REGEX.test(key)) { delete this.configurationProperties[key]; delete this.defaultLanguageConfigurationOverridesNode.properties![key]; @@ -375,9 +389,11 @@ class ConfigurationRegistry implements IConfigurationRegistry { continue; } - const property = properties[key]; + const property: IRegisteredConfigurationPropertySchema = properties[key]; + property.source = extensionInfo?.id; // update default value + property.defaultDefaultValue = properties[key].default; this.updatePropertyDefaultValue(key, property); // update scope @@ -415,19 +431,20 @@ class ConfigurationRegistry implements IConfigurationRegistry { return propertyKeys; } + // TODO: @sandy081 - Remove this method and include required info in getConfigurationProperties getConfigurations(): IConfigurationNode[] { return this.configurationContributors; } - getConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema } { + getConfigurationProperties(): IStringDictionary { return this.configurationProperties; } - getExcludedConfigurationProperties(): { [qualifiedKey: string]: IConfigurationPropertySchema } { + getExcludedConfigurationProperties(): IStringDictionary { return this.excludedConfigurationProperties; } - getConfigurationDefaultsOverrides(): IStringDictionary { + getConfigurationDefaultsOverrides(): Map { return this.configurationDefaultsOverrides; } @@ -531,15 +548,19 @@ class ConfigurationRegistry implements IConfigurationRegistry { this._onDidSchemaChange.fire(); } - private updatePropertyDefaultValue(key: string, property: IConfigurationPropertySchema): void { - let defaultValue = this.configurationDefaultsOverrides[key]; + private updatePropertyDefaultValue(key: string, property: IRegisteredConfigurationPropertySchema): void { + const configurationdefaultOverride = this.configurationDefaultsOverrides.get(key); + let defaultValue = configurationdefaultOverride?.value; + let defaultSource = configurationdefaultOverride?.source; if (types.isUndefined(defaultValue)) { - defaultValue = property.default; + defaultValue = property.defaultDefaultValue; + defaultSource = undefined; } if (types.isUndefined(defaultValue)) { defaultValue = getDefaultValue(property.type); } property.default = defaultValue; + property.defaultSource = defaultSource; } } diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index d9bd5b473beaa..5d539261a4fd6 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -96,9 +96,9 @@ export class DefaultConfiguration extends Disposable { private async updateCachedConfigurationDefaultsOverrides(): Promise { const cachedConfigurationDefaultsOverrides: IStringDictionary = {}; const configurationDefaultsOverrides = this.configurationRegistry.getConfigurationDefaultsOverrides(); - for (const key of Object.keys(configurationDefaultsOverrides)) { - if (!OVERRIDE_PROPERTY_REGEX.test(key) && configurationDefaultsOverrides[key] !== undefined) { - cachedConfigurationDefaultsOverrides[key] = configurationDefaultsOverrides[key]; + for (const [key, value] of configurationDefaultsOverrides) { + if (!OVERRIDE_PROPERTY_REGEX.test(key) && value.value !== undefined) { + cachedConfigurationDefaultsOverrides[key] = value.value; } } try { diff --git a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts index 29fa4cfa729e8..070094ecc0e82 100644 --- a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts @@ -44,7 +44,8 @@ suite('DefaultConfiguration', () => { teardown(() => { configurationRegistry.deregisterConfigurations(configurationRegistry.getConfigurations()); - configurationRegistry.deregisterDefaultConfigurations([{ overrides: configurationRegistry.getConfigurationDefaultsOverrides() }]); + const configurationDefaultsOverrides = configurationRegistry.getConfigurationDefaultsOverrides(); + configurationRegistry.deregisterDefaultConfigurations([...configurationDefaultsOverrides.keys()].map(key => ({ extensionId: configurationDefaultsOverrides.get(key)?.source, overrides: { [key]: configurationDefaultsOverrides.get(key)?.value } }))); }); test('configuration default overrides are read from environment', async () => { From 2b6fd1df46e2e3e787c916b2e9beef6e388ffc12 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 15:27:37 +0100 Subject: [PATCH 0043/2210] Extract `ZlibDeflateStream` --- src/vs/base/parts/ipc/node/ipc.net.ts | 99 ++++++++++++++++++--------- 1 file changed, 65 insertions(+), 34 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index d766f3f125c0d..0f4cac4304526 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -185,15 +185,14 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT public readonly socket: NodeSocket; public readonly permessageDeflate: boolean; - private _totalIncomingWireBytes: number; - private _totalIncomingDataBytes: number; - private _totalOutgoingWireBytes: number; - private _totalOutgoingDataBytes: number; + private _totalIncomingWireBytes: number = 0; + private _totalIncomingDataBytes: number = 0; + private _totalOutgoingWireBytes: number = 0; + private _totalOutgoingDataBytes: number = 0; private readonly _zlibInflateStream: ZlibInflateStream | null; - private readonly _zlibDeflate: zlib.DeflateRaw | null; + private readonly _zlibDeflateStream: ZlibDeflateStream | null; private _zlibDeflateFlushWaitingCount: number; private readonly _onDidZlibFlush = this._register(new Emitter()); - private readonly _pendingDeflateData: Buffer[] = []; private readonly _incomingData: ChunkStream; private readonly _onData = this._register(new Emitter()); private readonly _onClose = this._register(new Emitter()); @@ -251,18 +250,14 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT super(); this.socket = socket; this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'WebSocketNodeSocket', permessageDeflate, inflateBytesLength: inflateBytes?.byteLength || 0, recordInflateBytes }); - this._totalIncomingWireBytes = 0; - this._totalIncomingDataBytes = 0; - this._totalOutgoingWireBytes = 0; - this._totalOutgoingDataBytes = 0; this.permessageDeflate = permessageDeflate; if (permessageDeflate) { // See https://tools.ietf.org/html/rfc7692#page-16 // To simplify our logic, we don't negotiate the window size // and simply dedicate (2^15) / 32kb per web socket - this._zlibInflateStream = new ZlibInflateStream(this, recordInflateBytes, inflateBytes, { + this._zlibInflateStream = this._register(new ZlibInflateStream(this, recordInflateBytes, inflateBytes, { windowBits: 15 - }); + })); this._register(this._zlibInflateStream.onError((err) => { // zlib errors are fatal, since we have no idea how to recover console.error(err); @@ -278,11 +273,10 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT this._onData.fire(data); })); - this._zlibDeflate = zlib.createDeflateRaw({ + this._zlibDeflateStream = this._register(new ZlibDeflateStream(this, { windowBits: 15 - }); - this._zlibDeflate.on('error', (err) => { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (err)?.code }); + })); + this._register(this._zlibDeflateStream.onError((err) => { // zlib errors are fatal, since we have no idea how to recover console.error(err); onUnexpectedError(err); @@ -291,14 +285,10 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT hadError: true, error: err }); - }); - this._zlibDeflate.on('data', (data: Buffer) => { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateData, data); - this._pendingDeflateData.push(data); - }); + })); } else { this._zlibInflateStream = null; - this._zlibDeflate = null; + this._zlibDeflateStream = null; } this._zlibDeflateFlushWaitingCount = 0; this._incomingData = new ChunkStream(); @@ -333,24 +323,17 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT public write(buffer: VSBuffer): void { this._totalOutgoingDataBytes += buffer.byteLength; - if (this._zlibDeflate) { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateWrite, buffer.buffer); - this._zlibDeflate.write(buffer.buffer); + if (this._zlibDeflateStream) { + + this._zlibDeflateStream.write(buffer); this._zlibDeflateFlushWaitingCount++; - // See https://zlib.net/manual.html#Constants - this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => { - this.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateFlushFired); + this._zlibDeflateStream.flush((data) => { this._zlibDeflateFlushWaitingCount--; - let data = Buffer.concat(this._pendingDeflateData); - this._pendingDeflateData.length = 0; - - // See https://tools.ietf.org/html/rfc7692#section-7.2.1 - data = data.slice(0, data.length - 4); if (!this._isEnded) { // Avoid ERR_STREAM_WRITE_AFTER_END - this._write(VSBuffer.wrap(data), true); + this._write(data, true); } if (this._zlibDeflateFlushWaitingCount === 0) { @@ -590,6 +573,54 @@ class ZlibInflateStream extends Disposable { } } +class ZlibDeflateStream extends Disposable { + + private readonly _onError = this._register(new Emitter()); + public readonly onError = this._onError.event; + + private readonly _zlibDeflate: zlib.DeflateRaw; + private readonly _pendingDeflateData: VSBuffer[] = []; + + constructor( + private readonly _tracer: ISocketTracer, + options: zlib.ZlibOptions + ) { + super(); + + this._zlibDeflate = zlib.createDeflateRaw({ + windowBits: 15 + }); + this._zlibDeflate.on('error', (err) => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (err)?.code }); + this._onError.fire(err); + }); + this._zlibDeflate.on('data', (data: Buffer) => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateData, data); + this._pendingDeflateData.push(VSBuffer.wrap(data)); + }); + } + + public write(buffer: VSBuffer): void { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateWrite, buffer.buffer); + this._zlibDeflate.write(buffer.buffer); + } + + public flush(callback: (data: VSBuffer) => void): void { + // See https://zlib.net/manual.html#Constants + this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateFlushFired); + + let data = VSBuffer.concat(this._pendingDeflateData); + this._pendingDeflateData.length = 0; + + // See https://tools.ietf.org/html/rfc7692#section-7.2.1 + data = data.slice(0, data.byteLength - 4); + + callback(data); + }); + } +} + function unmask(buffer: VSBuffer, mask: number): void { if (mask === 0) { return; From a3dce400d6ede8d0c09b2893a09784f51251e26a Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 15:34:36 +0100 Subject: [PATCH 0044/2210] Move draining logic to `ZlibDeflateStream` --- src/vs/base/parts/ipc/node/ipc.net.ts | 42 +++++++++++++++++---------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 0f4cac4304526..4b3ae6bdef9c3 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -191,8 +191,6 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT private _totalOutgoingDataBytes: number = 0; private readonly _zlibInflateStream: ZlibInflateStream | null; private readonly _zlibDeflateStream: ZlibDeflateStream | null; - private _zlibDeflateFlushWaitingCount: number; - private readonly _onDidZlibFlush = this._register(new Emitter()); private readonly _incomingData: ChunkStream; private readonly _onData = this._register(new Emitter()); private readonly _onClose = this._register(new Emitter()); @@ -290,16 +288,15 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT this._zlibInflateStream = null; this._zlibDeflateStream = null; } - this._zlibDeflateFlushWaitingCount = 0; this._incomingData = new ChunkStream(); this._register(this.socket.onData(data => this._acceptChunk(data))); this._register(this.socket.onClose((e) => this._onClose.fire(e))); } public override dispose(): void { - if (this._zlibDeflateFlushWaitingCount > 0) { + if (this._zlibDeflateStream && this._zlibDeflateStream.needsDraining()) { // Wait for any outstanding writes to finish before disposing - this._register(this._onDidZlibFlush.event(() => { + this._register(this._zlibDeflateStream.onDidDrain(() => { this.dispose(); })); } else { @@ -324,21 +321,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT this._totalOutgoingDataBytes += buffer.byteLength; if (this._zlibDeflateStream) { - this._zlibDeflateStream.write(buffer); - - this._zlibDeflateFlushWaitingCount++; this._zlibDeflateStream.flush((data) => { - this._zlibDeflateFlushWaitingCount--; - if (!this._isEnded) { // Avoid ERR_STREAM_WRITE_AFTER_END this._write(data, true); } - - if (this._zlibDeflateFlushWaitingCount === 0) { - this._onDidZlibFlush.fire(); - } }); } else { this._write(buffer, false); @@ -502,8 +490,8 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT public async drain(): Promise { this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainBegin); - if (this._zlibDeflateFlushWaitingCount > 0) { - await Event.toPromise(this._onDidZlibFlush.event); + if (this._zlibDeflateStream) { + await this._zlibDeflateStream.drain(); } await this.socket.drain(); this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainEnd); @@ -578,8 +566,12 @@ class ZlibDeflateStream extends Disposable { private readonly _onError = this._register(new Emitter()); public readonly onError = this._onError.event; + private readonly _onDidDrain = this._register(new Emitter()); + public readonly onDidDrain = this._onDidDrain.event; + private readonly _zlibDeflate: zlib.DeflateRaw; private readonly _pendingDeflateData: VSBuffer[] = []; + private _flushWaitingCount: number = 0; constructor( private readonly _tracer: ISocketTracer, @@ -606,8 +598,12 @@ class ZlibDeflateStream extends Disposable { } public flush(callback: (data: VSBuffer) => void): void { + this._flushWaitingCount++; + // See https://zlib.net/manual.html#Constants this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => { + this._flushWaitingCount--; + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateFlushFired); let data = VSBuffer.concat(this._pendingDeflateData); @@ -617,8 +613,22 @@ class ZlibDeflateStream extends Disposable { data = data.slice(0, data.byteLength - 4); callback(data); + + if (this._flushWaitingCount === 0) { + this._onDidDrain.fire(); + } }); } + + public needsDraining(): boolean { + return (this._flushWaitingCount > 0); + } + + public async drain(): Promise { + if (this._flushWaitingCount > 0) { + await Event.toPromise(this.onDidDrain); + } + } } function unmask(buffer: VSBuffer, mask: number): void { From 10d3e93db52f2ee469231514169f0cf4fd3a7d69 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 25 Nov 2021 16:15:32 +0100 Subject: [PATCH 0045/2210] Improves assertNever type. --- src/vs/base/common/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 9f047e0caa97a..028b0c4eb5443 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -287,7 +287,7 @@ export function NotImplementedProxy(name: string): { new(): T } { }; } -export function assertNever(value: never, message = 'Unreachable') { +export function assertNever(value: never, message = 'Unreachable'): never { throw new Error(message); } From 7b3474abff37d9ebbe11e48ed8aa38bd077bdf2e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 16:44:30 +0100 Subject: [PATCH 0046/2210] Make sure websocket frames are processed in order --- src/vs/base/parts/ipc/node/ipc.net.ts | 290 ++++++++++-------- .../base/parts/ipc/test/node/ipc.net.test.ts | 9 + 2 files changed, 171 insertions(+), 128 deletions(-) diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 4b3ae6bdef9c3..af596a0fed690 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -184,13 +184,7 @@ interface ISocketTracer { export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketTracer { public readonly socket: NodeSocket; - public readonly permessageDeflate: boolean; - private _totalIncomingWireBytes: number = 0; - private _totalIncomingDataBytes: number = 0; - private _totalOutgoingWireBytes: number = 0; - private _totalOutgoingDataBytes: number = 0; - private readonly _zlibInflateStream: ZlibInflateStream | null; - private readonly _zlibDeflateStream: ZlibDeflateStream | null; + private readonly _flowManager: WebSocketFlowManager; private readonly _incomingData: ChunkStream; private readonly _onData = this._register(new Emitter()); private readonly _onClose = this._register(new Emitter()); @@ -205,27 +199,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT mask: 0 }; - public get totalIncomingWireBytes(): number { - return this._totalIncomingWireBytes; - } - - public get totalIncomingDataBytes(): number { - return this._totalIncomingDataBytes; - } - - public get totalOutgoingWireBytes(): number { - return this._totalOutgoingWireBytes; - } - - public get totalOutgoingDataBytes(): number { - return this._totalOutgoingDataBytes; + public get permessageDeflate(): boolean { + return this._flowManager.permessageDeflate; } public get recordedInflateBytes(): VSBuffer { - if (this._zlibInflateStream) { - return this._zlibInflateStream.recordedInflateBytes; - } - return VSBuffer.alloc(0); + return this._flowManager.recordedInflateBytes; } public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { @@ -248,55 +227,33 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT super(); this.socket = socket; this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'WebSocketNodeSocket', permessageDeflate, inflateBytesLength: inflateBytes?.byteLength || 0, recordInflateBytes }); - this.permessageDeflate = permessageDeflate; - if (permessageDeflate) { - // See https://tools.ietf.org/html/rfc7692#page-16 - // To simplify our logic, we don't negotiate the window size - // and simply dedicate (2^15) / 32kb per web socket - this._zlibInflateStream = this._register(new ZlibInflateStream(this, recordInflateBytes, inflateBytes, { - windowBits: 15 - })); - this._register(this._zlibInflateStream.onError((err) => { - // zlib errors are fatal, since we have no idea how to recover - console.error(err); - onUnexpectedError(err); - this._onClose.fire({ - type: SocketCloseEventType.NodeSocketCloseEvent, - hadError: true, - error: err - }); - })); - this._register(this._zlibInflateStream.onData((data) => { - this._totalIncomingDataBytes += data.byteLength; - this._onData.fire(data); - })); - - this._zlibDeflateStream = this._register(new ZlibDeflateStream(this, { - windowBits: 15 - })); - this._register(this._zlibDeflateStream.onError((err) => { - // zlib errors are fatal, since we have no idea how to recover - console.error(err); - onUnexpectedError(err); - this._onClose.fire({ - type: SocketCloseEventType.NodeSocketCloseEvent, - hadError: true, - error: err - }); - })); - } else { - this._zlibInflateStream = null; - this._zlibDeflateStream = null; - } + this._flowManager = this._register(new WebSocketFlowManager( + this, + permessageDeflate, + inflateBytes, + recordInflateBytes, + this._onData, + (data, compressed) => this._write(data, compressed) + )); + this._register(this._flowManager.onError((err) => { + // zlib errors are fatal, since we have no idea how to recover + console.error(err); + onUnexpectedError(err); + this._onClose.fire({ + type: SocketCloseEventType.NodeSocketCloseEvent, + hadError: true, + error: err + }); + })); this._incomingData = new ChunkStream(); this._register(this.socket.onData(data => this._acceptChunk(data))); this._register(this.socket.onClose((e) => this._onClose.fire(e))); } public override dispose(): void { - if (this._zlibDeflateStream && this._zlibDeflateStream.needsDraining()) { + if (this._flowManager.isProcessingWriteQueue()) { // Wait for any outstanding writes to finish before disposing - this._register(this._zlibDeflateStream.onDidDrain(() => { + this._register(this._flowManager.onDidFinishProcessingWriteQueue(() => { this.dispose(); })); } else { @@ -318,22 +275,15 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT } public write(buffer: VSBuffer): void { - this._totalOutgoingDataBytes += buffer.byteLength; - - if (this._zlibDeflateStream) { - this._zlibDeflateStream.write(buffer); - this._zlibDeflateStream.flush((data) => { - if (!this._isEnded) { - // Avoid ERR_STREAM_WRITE_AFTER_END - this._write(data, true); - } - }); - } else { - this._write(buffer, false); - } + this._flowManager.writeMessage(buffer); } private _write(buffer: VSBuffer, compressed: boolean): void { + if (this._isEnded) { + // Avoid ERR_STREAM_WRITE_AFTER_END + return; + } + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketWrite, buffer); let headerLen = Constants.MinHeaderByteSize; if (buffer.byteLength < 126) { @@ -371,7 +321,6 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset); } - this._totalOutgoingWireBytes += header.byteLength + buffer.byteLength; this.socket.write(VSBuffer.concat([header, buffer])); } @@ -384,7 +333,6 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT if (data.byteLength === 0) { return; } - this._totalIncomingWireBytes += data.byteLength; this._incomingData.acceptChunk(data); @@ -467,44 +415,152 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT this._state.readLen = Constants.MinHeaderByteSize; this._state.mask = 0; - if (this._zlibInflateStream && this._state.compressed) { - // See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2 - // Even if permessageDeflate is negotiated, it is possible - // that the other side might decide to send uncompressed messages - // So only decompress messages that have the RSV 1 bit set - // - // See https://tools.ietf.org/html/rfc7692#section-7.2.2 - - this._zlibInflateStream.write(body); - if (this._state.fin) { - this._zlibInflateStream.write(VSBuffer.fromByteArray([0x00, 0x00, 0xff, 0xff])); - } - this._zlibInflateStream.flush(); - } else { - this._totalIncomingDataBytes += body.byteLength; - this._onData.fire(body); - } + this._flowManager.acceptFrame(body, this._state.compressed, !!this._state.fin); } } } public async drain(): Promise { this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainBegin); - if (this._zlibDeflateStream) { - await this._zlibDeflateStream.drain(); + if (this._flowManager.isProcessingWriteQueue()) { + await Event.toPromise(this._flowManager.onDidFinishProcessingWriteQueue); } await this.socket.drain(); this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainEnd); } } -class ZlibInflateStream extends Disposable { +class WebSocketFlowManager extends Disposable { private readonly _onError = this._register(new Emitter()); public readonly onError = this._onError.event; - private readonly _onData = this._register(new Emitter()); - public readonly onData = this._onData.event; + private readonly _zlibInflateStream: ZlibInflateStream | null; + private readonly _zlibDeflateStream: ZlibDeflateStream | null; + private readonly _writeQueue: VSBuffer[] = []; + private readonly _readQueue: { data: VSBuffer, isCompressed: boolean, isLastFrameOfMessage: boolean }[] = []; + + private readonly _onDidFinishProcessingWriteQueue = this._register(new Emitter()); + public readonly onDidFinishProcessingWriteQueue = this._onDidFinishProcessingWriteQueue.event; + + public get permessageDeflate(): boolean { + return Boolean(this._zlibInflateStream && this._zlibDeflateStream); + } + + public get recordedInflateBytes(): VSBuffer { + if (this._zlibInflateStream) { + return this._zlibInflateStream.recordedInflateBytes; + } + return VSBuffer.alloc(0); + } + + constructor( + private readonly _tracer: ISocketTracer, + permessageDeflate: boolean, + inflateBytes: VSBuffer | null, + recordInflateBytes: boolean, + private readonly _onData: Emitter, + private readonly _writeFn: (data: VSBuffer, compressed: boolean) => void + ) { + super(); + if (permessageDeflate) { + // See https://tools.ietf.org/html/rfc7692#page-16 + // To simplify our logic, we don't negotiate the window size + // and simply dedicate (2^15) / 32kb per web socket + this._zlibInflateStream = this._register(new ZlibInflateStream(this._tracer, recordInflateBytes, inflateBytes, { windowBits: 15 })); + this._zlibDeflateStream = this._register(new ZlibDeflateStream(this._tracer, { windowBits: 15 })); + this._register(this._zlibInflateStream.onError((err) => this._onError.fire(err))); + this._register(this._zlibDeflateStream.onError((err) => this._onError.fire(err))); + } else { + this._zlibInflateStream = null; + this._zlibDeflateStream = null; + } + } + + public writeMessage(message: VSBuffer): void { + this._writeQueue.push(message); + this._processWriteQueue(); + } + + private _isProcessingWriteQueue = false; + private async _processWriteQueue(): Promise { + if (this._isProcessingWriteQueue) { + return; + } + this._isProcessingWriteQueue = true; + while (this._writeQueue.length > 0) { + const message = this._writeQueue.shift()!; + if (this._zlibDeflateStream) { + const data = await this._deflateMessage(this._zlibDeflateStream, message); + this._writeFn(data, true); + } else { + this._writeFn(message, false); + } + } + this._isProcessingWriteQueue = false; + this._onDidFinishProcessingWriteQueue.fire(); + } + + public isProcessingWriteQueue(): boolean { + return (this._isProcessingWriteQueue); + } + + /** + * Subsequent calls should wait for the previous `_deflateBuffer` call to complete. + */ + private _deflateMessage(zlibDeflateStream: ZlibDeflateStream, buffer: VSBuffer): Promise { + return new Promise((resolve, reject) => { + zlibDeflateStream.write(buffer); + zlibDeflateStream.flush(data => resolve(data)); + }); + } + + public acceptFrame(data: VSBuffer, isCompressed: boolean, isLastFrameOfMessage: boolean): void { + this._readQueue.push({ data, isCompressed, isLastFrameOfMessage }); + this._processReadQueue(); + } + + private _isProcessingReadQueue = false; + private async _processReadQueue(): Promise { + if (this._isProcessingReadQueue) { + return; + } + this._isProcessingReadQueue = true; + while (this._readQueue.length > 0) { + const frameInfo = this._readQueue.shift()!; + if (this._zlibInflateStream && frameInfo.isCompressed) { + // See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2 + // Even if permessageDeflate is negotiated, it is possible + // that the other side might decide to send uncompressed messages + // So only decompress messages that have the RSV 1 bit set + const data = await this._inflateFrame(this._zlibInflateStream, frameInfo.data, frameInfo.isLastFrameOfMessage); + this._onData.fire(data); + } else { + this._onData.fire(frameInfo.data); + } + } + this._isProcessingReadQueue = false; + } + + /** + * Subsequent calls should wait for the previous `transformRead` call to complete. + */ + private _inflateFrame(zlibInflateStream: ZlibInflateStream, buffer: VSBuffer, isLastFrameOfMessage: boolean): Promise { + return new Promise((resolve, reject) => { + // See https://tools.ietf.org/html/rfc7692#section-7.2.2 + zlibInflateStream.write(buffer); + if (isLastFrameOfMessage) { + zlibInflateStream.write(VSBuffer.fromByteArray([0x00, 0x00, 0xff, 0xff])); + } + zlibInflateStream.flush(data => resolve(data)); + }); + } +} + +class ZlibInflateStream extends Disposable { + + private readonly _onError = this._register(new Emitter()); + public readonly onError = this._onError.event; private readonly _zlibInflate: zlib.InflateRaw; private readonly _recordedInflateBytes: VSBuffer[] = []; @@ -551,12 +607,12 @@ class ZlibInflateStream extends Disposable { this._zlibInflate.write(buffer.buffer); } - public flush(): void { + public flush(callback: (data: VSBuffer) => void): void { this._zlibInflate.flush(() => { this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateFlushFired); const data = VSBuffer.concat(this._pendingInflateData); this._pendingInflateData.length = 0; - this._onData.fire(data); + callback(data); }); } } @@ -566,12 +622,8 @@ class ZlibDeflateStream extends Disposable { private readonly _onError = this._register(new Emitter()); public readonly onError = this._onError.event; - private readonly _onDidDrain = this._register(new Emitter()); - public readonly onDidDrain = this._onDidDrain.event; - private readonly _zlibDeflate: zlib.DeflateRaw; private readonly _pendingDeflateData: VSBuffer[] = []; - private _flushWaitingCount: number = 0; constructor( private readonly _tracer: ISocketTracer, @@ -598,12 +650,8 @@ class ZlibDeflateStream extends Disposable { } public flush(callback: (data: VSBuffer) => void): void { - this._flushWaitingCount++; - // See https://zlib.net/manual.html#Constants this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => { - this._flushWaitingCount--; - this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateFlushFired); let data = VSBuffer.concat(this._pendingDeflateData); @@ -613,22 +661,8 @@ class ZlibDeflateStream extends Disposable { data = data.slice(0, data.byteLength - 4); callback(data); - - if (this._flushWaitingCount === 0) { - this._onDidDrain.fire(); - } }); } - - public needsDraining(): boolean { - return (this._flushWaitingCount > 0); - } - - public async drain(): Promise { - if (this._flushWaitingCount > 0) { - await Event.toPromise(this.onDidDrain); - } - } } function unmask(buffer: VSBuffer, mask: number): void { diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 4345fcccaca5a..042bfcbabfef5 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -525,5 +525,14 @@ suite('WebSocketNodeSocket', () => { const actual = await testReading(frames, true); assert.deepStrictEqual(actual, 'Hello'); }); + + test('A single-frame compressed text message followed by a single-frame non-compressed text message', async () => { + const frames = [ + [0xc1, 0x07, 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00], // contains "Hello" + [0x81, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64] // contains "world" + ]; + const actual = await testReading(frames, true); + assert.deepStrictEqual(actual, 'Helloworld'); + }); }); }); From dd19b1d50bbff6676e7d71ce7ff8e9016c6786b2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 25 Nov 2021 15:31:48 +0100 Subject: [PATCH 0047/2210] refactor extensions report to extensions control manifest --- .../abstractExtensionManagementService.ts | 26 +++++++++---------- .../common/extensionGalleryService.ts | 18 ++++++------- .../common/extensionManagement.ts | 9 +++---- .../common/extensionManagementIpc.ts | 8 +++--- .../common/extensionManagementUtil.ts | 10 +++---- .../extensions/browser/extensionsViewlet.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 2 +- .../extensionRecommendationsService.test.ts | 2 +- .../extensionsActions.test.ts | 2 +- .../electron-browser/extensionsViews.test.ts | 4 +-- .../extensionsWorkbenchService.test.ts | 2 +- .../common/extensionManagementService.ts | 13 ++++++---- 12 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index ea58b5a4aba9f..117e765ce8b3a 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { DidUninstallExtensionEvent, ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOperation, InstallOptions, - InstallVSIXOptions, IReportedExtension, StatisticType, UninstallOptions, TargetPlatform, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode + InstallVSIXOptions, IExtensionsControlManifest, StatisticType, UninstallOptions, TargetPlatform, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; @@ -46,7 +46,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl declare readonly _serviceBrand: undefined; - private reportedExtensions: Promise | undefined; + private extensionsControlManifest: Promise | undefined; private lastReportTimestamp = 0; private readonly installingExtensions = new Map(); private readonly uninstallingExtensions = new Map(); @@ -120,15 +120,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl await this.installFromGallery(galleryExtension); } - getExtensionsReport(): Promise { + getExtensionsControlManifest(): Promise { const now = new Date().getTime(); - if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness - this.reportedExtensions = this.updateReportCache(); + if (!this.extensionsControlManifest || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness + this.extensionsControlManifest = this.updateControlCache(); this.lastReportTimestamp = now; } - return this.reportedExtensions; + return this.extensionsControlManifest; } registerParticipant(participant: IExtensionManagementParticipant): void { @@ -403,7 +403,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } private async isMalicious(extension: IGalleryExtension): Promise { - const report = await this.getExtensionsReport(); + const report = await this.getExtensionsControlManifest(); return getMaliciousExtensionsSet(report).has(extension.identifier.id); } @@ -579,15 +579,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return galleryResult.firstPage[0]; } - private async updateReportCache(): Promise { + private async updateControlCache(): Promise { try { this.logService.trace('ExtensionManagementService.refreshReportedCache'); - const result = await this.galleryService.getExtensionsReport(); - this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`); - return result; + const manifest = await this.galleryService.getExtensionsControlManifest(); + this.logService.trace(`ExtensionManagementService.refreshControlCache`, manifest); + return manifest; } catch (err) { - this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report'); - return []; + this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest'); + return { malicious: [] }; } } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 02de3658557ba..8585f92a88480 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { DefaultIconPath, getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, toTargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { DefaultIconPath, getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isIExtensionIdentifier, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, toTargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; @@ -438,7 +438,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller }; } -interface IRawExtensionsReport { +interface IRawExtensionsControlManifest { malicious: string[]; slow: string[]; } @@ -942,13 +942,13 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return engine; } - async getExtensionsReport(): Promise { + async getExtensionsControlManifest(): Promise { if (!this.isEnabled()) { throw new Error('No extension gallery service configured.'); } if (!this.extensionsControlUrl) { - return []; + return { malicious: [] }; } const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None); @@ -956,18 +956,16 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi throw new Error('Could not get extensions report.'); } - const result = await asJson(context); - const map = new Map(); + const result = await asJson(context); + const malicious: IExtensionIdentifier[] = []; if (result) { for (const id of result.malicious) { - const ext = map.get(id) || { id: { id }, malicious: true, slow: false }; - ext.malicious = true; - map.set(id, ext); + malicious.push({ id }); } } - return [...map.values()]; + return { malicious }; } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 3d7442a342389..3964f4a6a95aa 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -310,9 +310,8 @@ export const enum StatisticType { Uninstall = 'uninstall' } -export interface IReportedExtension { - id: IExtensionIdentifier; - malicious: boolean; +export interface IExtensionsControlManifest { + malicious: IExtensionIdentifier[]; } export const enum InstallOperation { @@ -338,7 +337,7 @@ export interface IExtensionGalleryService { getManifest(extension: IGalleryExtension, token: CancellationToken): Promise; getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise; getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise; - getExtensionsReport(): Promise; + getExtensionsControlManifest(): Promise; isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; getCompatibleExtension(id: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; @@ -412,7 +411,7 @@ export interface IExtensionManagementService { uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType, donotIgnoreInvalidExtensions?: boolean): Promise; - getExtensionsReport(): Promise; + getExtensionsControlManifest(): Promise; updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise; updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 5d92423d69013..3141cf698553a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -9,7 +9,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOptions, InstallVSIXOptions, IReportedExtension, isTargetPlatformCompatible, TargetPlatform, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOptions, InstallVSIXOptions, IExtensionsControlManifest, isTargetPlatformCompatible, TargetPlatform, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI { @@ -72,7 +72,7 @@ export class ExtensionManagementChannel implements IServerChannel { case 'getInstalled': return this.service.getInstalled(args[0]).then(extensions => extensions.map(e => transformOutgoingExtension(e, uriTransformer))); case 'updateMetadata': return this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer)); case 'updateExtensionScope': return this.service.updateExtensionScope(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer)); - case 'getExtensionsReport': return this.service.getExtensionsReport(); + case 'getExtensionsControlManifest': return this.service.getExtensionsControlManifest(); } throw new Error('Invalid call'); @@ -169,8 +169,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt .then(extension => transformIncomingExtension(extension, null)); } - getExtensionsReport(): Promise { - return Promise.resolve(this.channel.call('getExtensionsReport')); + getExtensionsControlManifest(): Promise { + return Promise.resolve(this.channel.call('getExtensionsControlManifest')); } registerParticipant() { throw new Error('Not Supported'); } diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index dcf1a84bac3fb..e82c61f474989 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { compareIgnoreCase } from 'vs/base/common/strings'; -import { IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, ILocalExtension, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, ILocalExtension, IExtensionsControlManifest } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionIdentifier, IExtension } from 'vs/platform/extensions/common/extensions'; export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean { @@ -117,12 +117,12 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension): export const BetterMergeId = new ExtensionIdentifier('pprice.better-merge'); -export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set { +export function getMaliciousExtensionsSet(manifest: IExtensionsControlManifest): Set { const result = new Set(); - for (const extension of report) { - if (extension.malicious) { - result.add(extension.id.id); + if (manifest.malicious) { + for (const extension of manifest.malicious) { + result.add(extension.id); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index de0fad38d2117..acf81dc6bfe5f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -825,7 +825,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { } private checkForMaliciousExtensions(): Promise { - return this.extensionsManagementService.getExtensionsReport().then(report => { + return this.extensionsManagementService.getExtensionsControlManifest().then(report => { const maliciousSet = getMaliciousExtensionsSet(report); return this.extensionsManagementService.getInstalled(ExtensionType.User).then(installed => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 7e33ad4a6405a..9d9dff33d8e12 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -749,7 +749,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension options.text = options.text ? this.resolveQueryText(options.text) : options.text; options.includePreRelease = isUndefined(options.includePreRelease) ? this.preferPreReleases : options.includePreRelease; - const report = await this.extensionManagementService.getExtensionsReport(); + const report = await this.extensionManagementService.getExtensionsControlManifest(); const maliciousSet = getMaliciousExtensionsSet(report); try { const result = await this.galleryService.query(options, token); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index d6966d438b3c5..7f7e90d512ee5 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -218,7 +218,7 @@ suite('ExtensionRecommendationsService Test', () => { onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, async canInstall() { return true; }, - async getExtensionsReport() { return []; }, + async getExtensionsControlManifest() { return { malicious: [] }; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); } }); instantiationService.stub(IExtensionService, >{ diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index d35aa41bb4af1..d67171bd069b6 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -101,7 +101,7 @@ async function setupTest() { onUninstallExtension: uninstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, - async getExtensionsReport() { return []; }, + async getExtensionsControlManifest() { return { malicious: [] }; }, async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata) { local.identifier.uuid = metadata.id; local.publisherDisplayName = metadata.publisherDisplayName; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index b7edf47f47480..3b08938208afb 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -98,7 +98,7 @@ suite('ExtensionsListView Tests', () => { onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, async canInstall() { return true; }, - async getExtensionsReport() { return []; }, + async getExtensionsControlManifest() { return { malicious: [] }; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); } }); instantiationService.stub(IRemoteAgentService, RemoteAgentService); @@ -163,7 +163,7 @@ suite('ExtensionsListView Tests', () => { setup(async () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localEnabledTheme, localEnabledLanguage, localRandom, localDisabledTheme, localDisabledLanguage, builtInTheme, builtInBasic]); - instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []); + instantiationService.stubPromise(IExtensionManagementService, 'getExtensgetExtensionsControlManifestionsReport', {}); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index c3a706d67cdf9..4b4320e05fc5e 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -94,7 +94,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { onUninstallExtension: uninstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, - async getExtensionsReport() { return []; }, + async getExtensionsControlManifest() { return { malicious: [] }; }, async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata) { local.identifier.uuid = metadata.id; local.publisherDisplayName = metadata.publisherDisplayName; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 004a6db0ce030..2f61cd1075d5c 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -5,7 +5,7 @@ import { Event, EventMultiplexer } from 'vs/base/common/event'; import { - ILocalExtension, IGalleryExtension, IExtensionIdentifier, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, TargetPlatform, ExtensionManagementError, ExtensionManagementErrorCode + ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IGalleryMetadata, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, TargetPlatform, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage } from 'vs/platform/extensions/common/extensions'; @@ -369,14 +369,17 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return false; } - getExtensionsReport(): Promise { + getExtensionsControlManifest(): Promise { if (this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsReport(); + return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsControlManifest(); } if (this.extensionManagementServerService.remoteExtensionManagementServer) { - return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getExtensionsReport(); + return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getExtensionsControlManifest(); } - return Promise.resolve([]); + if (this.extensionManagementServerService.webExtensionManagementServer) { + return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getExtensionsControlManifest(); + } + return Promise.resolve({ malicious: [] }); } private getServer(extension: ILocalExtension): IExtensionManagementServer | null { From ab394ee788a59470092e1e8cd3e404b565c99f59 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 25 Nov 2021 19:55:21 +0100 Subject: [PATCH 0048/2210] #15756 support pre-release while installing through cli and commands --- src/vs/code/node/cliProcessMain.ts | 5 +++-- src/vs/platform/environment/common/argv.ts | 1 + src/vs/platform/environment/node/argv.ts | 1 + .../common/extensionManagement.ts | 2 +- .../common/extensionManagementCLIService.ts | 10 +++++----- .../userDataSync/common/extensionsSync.ts | 2 +- src/vs/server/remoteAgentEnvironmentImpl.ts | 7 ++++--- src/vs/server/remoteExtensionHostAgentCli.ts | 5 +++-- src/vs/server/serverEnvironmentService.ts | 1 + .../api/browser/mainThreadCLICommands.ts | 2 +- .../extensions/browser/extensions.contribution.ts | 15 ++++++++++++--- .../common/extensionManagementService.ts | 3 ++- .../services/userData/browser/userDataInit.ts | 2 +- 13 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index fdee012d7ea10..f5a699ea72e87 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -22,7 +22,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { IFileService } from 'vs/platform/files/common/files'; @@ -217,7 +217,8 @@ class CliMain extends Disposable { // Install Extension else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) { - return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], !!this.argv['do-not-sync'], !!this.argv['force']); + const installOptions: InstallOptions = { isMachineScoped: !!this.argv['do-not-sync'], installPreReleaseVersion: !!this.argv['pre-release'] }; + return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], installOptions, !!this.argv['force']); } // Uninstall Extension diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 326a44308322a..6278d173a4b78 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -58,6 +58,7 @@ export interface NativeParsedArgs { 'show-versions'?: boolean; 'category'?: string; 'install-extension'?: string[]; // undefined or array of 1 or more + 'pre-release'?: boolean; 'install-builtin-extension'?: string[]; // undefined or array of 1 or more 'uninstall-extension'?: string[]; // undefined or array of 1 or more 'locate-extension'?: string[]; // undefined or array of 1 or more diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 763442884a9b7..75d91f3eebf1e 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -56,6 +56,7 @@ export const OPTIONS: OptionDescriptions> = { 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extensions.") }, 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions."), args: 'category' }, 'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. The identifier of an extension is always `${publisher}.${name}`. Use `--force` argument to update to latest version. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") }, + 'pre-release': { type: 'boolean', cat: 'e', description: localize('install prerelease', "Installs the pre-release version of the extension, when using --install-extension") }, 'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, 'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 3964f4a6a95aa..fe4a211e5a66a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -482,7 +482,7 @@ export interface IExtensionManagementCLIService { readonly _serviceBrand: undefined; listExtensions(showVersions: boolean, category?: string, output?: CLIOutput): Promise; - installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean, output?: CLIOutput): Promise; + installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], installOptions: InstallOptions, force: boolean, output?: CLIOutput): Promise; uninstallExtensions(extensions: (string | URI)[], force: boolean, output?: CLIOutput): Promise; locateExtension(extensions: string[], output?: CLIOutput): Promise; } diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts index 23ee9e4f08aa5..678fb6c5b431c 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts @@ -89,7 +89,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer } } - public async installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], isMachineScoped: boolean, force: boolean, output: CLIOutput = console): Promise { + public async installExtensions(extensions: (string | URI)[], builtinExtensionIds: string[], installOptions: InstallOptions, force: boolean, output: CLIOutput = console): Promise { const failed: string[] = []; const installedExtensionsManifests: IExtensionManifest[] = []; if (extensions.length) { @@ -119,21 +119,21 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer } else { const [id, version] = getIdAndVersion(extension); if (checkIfNotInstalled(id, version)) { - installExtensionInfos.push({ id, version, installOptions: { isBuiltin: false, isMachineScoped } }); + installExtensionInfos.push({ id, version, installOptions: { ...installOptions, isBuiltin: false } }); } } } for (const extension of builtinExtensionIds) { const [id, version] = getIdAndVersion(extension); if (checkIfNotInstalled(id, version)) { - installExtensionInfos.push({ id, version, installOptions: { isBuiltin: true, isMachineScoped: false } }); + installExtensionInfos.push({ id, version, installOptions: { ...installOptions, isBuiltin: true } }); } } if (vsixs.length) { await Promise.all(vsixs.map(async vsix => { try { - const manifest = await this.installVSIX(vsix, { isBuiltin: false, isMachineScoped }, force, output); + const manifest = await this.installVSIX(vsix, { ...installOptions, isBuiltin: false }, force, output); if (manifest) { installedExtensionsManifests.push(manifest); } @@ -200,7 +200,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer private async getGalleryExtensions(extensions: InstallExtensionInfo[]): Promise> { const galleryExtensions = new Map(); - const result = await this.extensionGalleryService.getExtensions(extensions, CancellationToken.None); + const result = await this.extensionGalleryService.getExtensions(extensions, extensions.some(e => e.installOptions.installPreReleaseVersion), CancellationToken.None); for (const extension of result) { galleryExtensions.set(extension.identifier.id.toLowerCase(), extension); } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 7124a8fd052b8..70eb3884a1399 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -416,7 +416,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse ) { if (await this.extensionManagementService.canInstall(extension)) { this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version); - await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease } /* pass options to prevent install and sync dialog in web */); + await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease } /* set isMachineScoped value to prevent install and sync dialog in web */); this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version); removeFromSkipped.push(extension.identifier); } else { diff --git a/src/vs/server/remoteAgentEnvironmentImpl.ts b/src/vs/server/remoteAgentEnvironmentImpl.ts index fe2a992dc406b..988722b559c16 100644 --- a/src/vs/server/remoteAgentEnvironmentImpl.ts +++ b/src/vs/server/remoteAgentEnvironmentImpl.ts @@ -27,7 +27,7 @@ import { ProcessItem } from 'vs/base/common/processes'; import { ILog, Translations } from 'vs/workbench/services/extensions/common/extensionPoints'; import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { IBuiltInExtension } from 'vs/base/common/product'; -import { IExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementCLIService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { cwd } from 'vs/base/common/process'; import { IRemoteTelemetryService } from 'vs/server/remoteTelemetryService'; import { Promises } from 'vs/base/node/pfs'; @@ -77,7 +77,8 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { }; if (environmentService.args['install-builtin-extension']) { - this.whenExtensionsReady = extensionManagementCLIService.installExtensions([], environmentService.args['install-builtin-extension'], !!environmentService.args['do-not-sync'], !!environmentService.args['force']) + const installOptions: InstallOptions = { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }; + this.whenExtensionsReady = extensionManagementCLIService.installExtensions([], environmentService.args['install-builtin-extension'], installOptions, !!environmentService.args['force']) .then(null, error => { logService.error(error); }); @@ -89,7 +90,7 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { if (extensionsToInstall) { const idsOrVSIX = extensionsToInstall.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input); this.whenExtensionsReady - .then(() => extensionManagementCLIService.installExtensions(idsOrVSIX, [], !!environmentService.args['do-not-sync'], !!environmentService.args['force'])) + .then(() => extensionManagementCLIService.installExtensions(idsOrVSIX, [], { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }, !!environmentService.args['force'])) .then(null, error => { logService.error(error); }); diff --git a/src/vs/server/remoteExtensionHostAgentCli.ts b/src/vs/server/remoteExtensionHostAgentCli.ts index d375069f5f0e0..d52350adbd3a7 100644 --- a/src/vs/server/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/remoteExtensionHostAgentCli.ts @@ -12,7 +12,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -114,7 +114,8 @@ class CliMain extends Disposable { // Install Extension else if (this.args['install-extension'] || this.args['install-builtin-extension']) { - return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.args['install-extension'] || []), this.args['install-builtin-extension'] || [], !!this.args['do-not-sync'], !!this.args['force']); + const installOptions: InstallOptions = { isMachineScoped: !!this.args['do-not-sync'], installPreReleaseVersion: !!this.args['pre-release'] }; + return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.args['install-extension'] || []), this.args['install-builtin-extension'] || [], installOptions, !!this.args['force']); } // Uninstall Extension diff --git a/src/vs/server/serverEnvironmentService.ts b/src/vs/server/serverEnvironmentService.ts index d59b63c492cf6..86aa2f00eef29 100644 --- a/src/vs/server/serverEnvironmentService.ts +++ b/src/vs/server/serverEnvironmentService.ts @@ -120,6 +120,7 @@ export interface ServerParsedArgs { force?: boolean; // used by install-extension 'do-not-sync'?: boolean; // used by install-extension + 'pre-release'?: boolean; // used by install-extension 'user-data-dir'?: string; 'builtin-extensions-dir'?: string; diff --git a/src/vs/workbench/api/browser/mainThreadCLICommands.ts b/src/vs/workbench/api/browser/mainThreadCLICommands.ts index 1fbb67106bc2e..863d73cb679e7 100644 --- a/src/vs/workbench/api/browser/mainThreadCLICommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCLICommands.ts @@ -71,7 +71,7 @@ CommandsRegistry.registerCommand('_remoteCLI.manageExtensions', async function ( const revive = (inputs: (string | UriComponents)[]) => inputs.map(input => isString(input) ? input : URI.revive(input)); if (Array.isArray(args.install) && args.install.length) { try { - await cliService.installExtensions(revive(args.install), [], true, !!args.force, output); + await cliService.installExtensions(revive(args.install), [], { isMachineScoped: true }, !!args.force, output); } catch (e) { lines.push(e.message); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 00c9e0370180e..c114661a85a11 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -8,7 +8,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { MenuRegistry, MenuId, registerAction2, Action2, ISubmenuItem, IMenuItem, IAction2Options } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, InstallOperation, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -274,6 +274,11 @@ CommandsRegistry.registerCommand({ 'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."), default: false }, + 'installPreReleaseVersion': { + 'type': 'boolean', + 'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, VS Code installs the pre-release version of the extension if available."), + default: false + }, 'donotSync': { 'type': 'boolean', 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."), @@ -284,14 +289,18 @@ CommandsRegistry.registerCommand({ } ] }, - handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean, donotSync?: boolean }) => { + handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean, installPreReleaseVersion?: boolean, donotSync?: boolean }) => { const extensionManagementService = accessor.get(IExtensionManagementService); const extensionGalleryService = accessor.get(IExtensionGalleryService); try { if (typeof arg === 'string') { const [extension] = await extensionGalleryService.getExtensions([{ id: arg }], CancellationToken.None); if (extension) { - await extensionManagementService.installFromGallery(extension, options?.donotSync ? { isMachineScoped: true } : undefined); + const installOptions: InstallOptions = { + isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */ + installPreReleaseVersion: options?.installPreReleaseVersion + }; + await extensionManagementService.installFromGallery(extension, installOptions); } else { throw new Error(localize('notFound', "Extension '{0}' not found.", arg)); } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 2f61cd1075d5c..d21b3f6298fc3 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -28,6 +28,7 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { isUndefined } from 'vs/base/common/types'; export class ExtensionManagementService extends Disposable implements IWorkbenchExtensionManagementService { @@ -292,7 +293,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench } if (servers.length) { - if (!installOptions) { + if (!installOptions || isUndefined(installOptions.isMachineScoped)) { const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]); installOptions = { isMachineScoped, isBuiltin: false }; } diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index 5dbf5aa9f64b0..1fb4714891aa7 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -395,7 +395,7 @@ class NewExtensionsInitializer implements IUserDataInitializer { storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService); } this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); - const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: extensionToSync.preRelease } /* pass options to prevent install and sync dialog in web */); + const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: extensionToSync.preRelease } /* set isMachineScoped to prevent install and sync dialog in web */); if (!preview.disabledExtensions.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { newlyEnabledExtensions.push(local); } From 60d21965c76373a784fe8042f8e6108708bbac62 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 25 Nov 2021 14:57:03 +0100 Subject: [PATCH 0049/2210] add marketplace apis for file/product icon themes (for #137289) --- .../themes/browser/themes.contribution.ts | 2 +- .../themes/browser/workbenchThemeService.ts | 123 ++++++++++++------ .../themes/common/themeExtensionPoints.ts | 9 +- .../themes/common/workbenchThemeService.ts | 12 +- 4 files changed, 95 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 2119f146186fa..aa2fc7bb01514 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -307,7 +307,7 @@ class MarketplaceThemes { const ext = gallery[i]; if (!installedExtensions.has(ext.identifier.id) && !this._marketplaceExtensions.has(ext.identifier.id)) { this._marketplaceExtensions.add(ext.identifier.id); - const themes = await this.themeService.getMarketplaceColorThemes(ext.identifier.id, ext.version); + const themes = await this.themeService.getMarketplaceColorThemes(ext.publisher, ext.name, ext.version); for (const theme of themes) { this._marketplaceThemes.push({ id: theme.id, theme: theme, label: theme.label, description: `${ext.displayName} · ${ext.publisherDisplayName}`, galleryExtension: ext, buttons: [configureButton] }); } diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 21e03d8f81ad0..09ef85d453a1c 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -407,21 +407,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.colorThemeRegistry.getThemes(); } - public async getMarketplaceColorThemes(id: string, version: string): Promise { - const [publisher, name] = id.split('.'); + public async getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise { const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version }, 'extension'); - if (!extensionLocation) { - return []; - } - try { - const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json')); - - const data: ExtensionData = { extensionPublisher: publisher, extensionId: id, extensionName: name, extensionIsBuiltin: false }; - - return this.colorThemeRegistry.getMarketplaceThemes(JSON.parse(manifestContent), extensionLocation, data); - - } catch (e) { - this.logService.error(`Problem loading themes from marketplace ${e}`); + if (extensionLocation) { + try { + const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json')); + return this.colorThemeRegistry.getMarketplaceThemes(JSON.parse(manifestContent), extensionLocation, ExtensionData.fromName(publisher, name)); + } catch (e) { + this.logService.error('Problem loading themes from marketplace', e); + } } return []; } @@ -593,13 +587,21 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.onFileIconThemeChange.event; } - - public async setFileIconTheme(iconTheme: string | undefined, settingsTarget: ThemeSettingTarget): Promise { + public async setFileIconTheme(iconThemeOrId: string | undefined | IWorkbenchFileIconTheme, settingsTarget: ThemeSettingTarget): Promise { return this.fileIconThemeSequencer.queue(async () => { - iconTheme = iconTheme || ''; - if (iconTheme !== this.currentFileIconTheme.id || !this.currentFileIconTheme.isLoaded) { + if (iconThemeOrId === undefined) { + iconThemeOrId = ''; + } + const themeId = types.isString(iconThemeOrId) ? iconThemeOrId : iconThemeOrId.id; + if (themeId !== this.currentFileIconTheme.id || !this.currentFileIconTheme.isLoaded) { - const newThemeData = this.fileIconThemeRegistry.findThemeById(iconTheme) || FileIconThemeData.noIconTheme; + let newThemeData = this.fileIconThemeRegistry.findThemeById(themeId); + if (!newThemeData && iconThemeOrId instanceof FileIconThemeData) { + newThemeData = iconThemeOrId; + } + if (!newThemeData) { + newThemeData = FileIconThemeData.noIconTheme; + } await newThemeData.ensureLoaded(this.extensionResourceLoaderService); this.applyAndSetFileIconTheme(newThemeData); // updates this.currentFileIconTheme @@ -617,6 +619,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } + public async getMarketplaceFileIconThemes(publisher: string, name: string, version: string): Promise { + const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version }, 'extension'); + if (extensionLocation) { + try { + const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json')); + return this.fileIconThemeRegistry.getMarketplaceThemes(JSON.parse(manifestContent), extensionLocation, ExtensionData.fromName(publisher, name)); + } catch (e) { + this.logService.error('Problem loading themes from marketplace', e); + } + } + return []; + } + private async reloadCurrentFileIconTheme() { return this.fileIconThemeSequencer.queue(async () => { await this.currentFileIconTheme.reload(this.extensionResourceLoaderService); @@ -625,15 +640,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } public async restoreFileIconTheme(): Promise { - const settingId = this.settings.fileIconTheme; - const theme = this.fileIconThemeRegistry.findThemeBySettingsId(settingId); - if (theme) { - if (settingId !== this.currentFileIconTheme.settingsId) { - await this.setFileIconTheme(theme.id, undefined); + return this.fileIconThemeSequencer.queue(async () => { + const settingId = this.settings.fileIconTheme; + const theme = this.fileIconThemeRegistry.findThemeBySettingsId(settingId); + if (theme) { + if (settingId !== this.currentFileIconTheme.settingsId) { + await this.setFileIconTheme(theme.id, undefined); + } else if (theme !== this.currentFileIconTheme) { + this.applyAndSetFileIconTheme(theme, true); + } + return true; } - return true; - } - return false; + return false; + }); } private applyAndSetFileIconTheme(iconThemeData: FileIconThemeData, silent = false): void { @@ -670,11 +689,20 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.onProductIconThemeChange.event; } - public async setProductIconTheme(iconTheme: string | undefined, settingsTarget: ThemeSettingTarget): Promise { + public async setProductIconTheme(iconThemeOrId: string | undefined | IWorkbenchProductIconTheme, settingsTarget: ThemeSettingTarget): Promise { return this.productIconThemeSequencer.queue(async () => { - iconTheme = iconTheme || ''; - if (iconTheme !== this.currentProductIconTheme.id || !this.currentProductIconTheme.isLoaded) { - const newThemeData = this.productIconThemeRegistry.findThemeById(iconTheme) || ProductIconThemeData.defaultTheme; + if (iconThemeOrId === undefined) { + iconThemeOrId = ''; + } + const themeId = types.isString(iconThemeOrId) ? iconThemeOrId : iconThemeOrId.id; + if (themeId !== this.currentProductIconTheme.id || !this.currentProductIconTheme.isLoaded) { + let newThemeData = this.productIconThemeRegistry.findThemeById(themeId); + if (!newThemeData && iconThemeOrId instanceof ProductIconThemeData) { + newThemeData = iconThemeOrId; + } + if (!newThemeData) { + newThemeData = ProductIconThemeData.defaultTheme; + } await newThemeData.ensureLoaded(this.extensionResourceLoaderService, this.logService); this.applyAndSetProductIconTheme(newThemeData); // updates this.currentProductIconTheme @@ -691,6 +719,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } + public async getMarketplaceProductIconThemes(publisher: string, name: string, version: string): Promise { + const extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ publisher, name, version }, 'extension'); + if (extensionLocation) { + try { + const manifestContent = await this.extensionResourceLoaderService.readExtensionResource(resources.joinPath(extensionLocation, 'package.json')); + return this.productIconThemeRegistry.getMarketplaceThemes(JSON.parse(manifestContent), extensionLocation, ExtensionData.fromName(publisher, name)); + } catch (e) { + this.logService.error('Problem loading themes from marketplace', e); + } + } + return []; + } + private async reloadCurrentProductIconTheme() { return this.productIconThemeSequencer.queue(async () => { await this.currentProductIconTheme.reload(this.extensionResourceLoaderService, this.logService); @@ -699,15 +740,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } public async restoreProductIconTheme(): Promise { - const settingId = this.settings.productIconTheme; - const theme = this.productIconThemeRegistry.findThemeBySettingsId(settingId); - if (theme) { - if (settingId !== this.currentProductIconTheme.settingsId) { - await this.setProductIconTheme(theme.id, undefined); + return this.productIconThemeSequencer.queue(async () => { + const settingId = this.settings.productIconTheme; + const theme = this.productIconThemeRegistry.findThemeBySettingsId(settingId); + if (theme) { + if (settingId !== this.currentProductIconTheme.settingsId) { + await this.setProductIconTheme(theme.id, undefined); + } else if (theme !== this.currentProductIconTheme) { + this.applyAndSetProductIconTheme(theme, true); + } + return true; } - return true; - } - return false; + return false; + }); } private applyAndSetProductIconTheme(iconThemeData: ProductIconThemeData, silent = false): void { diff --git a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts index ae8ae9701ffb3..5382bea849921 100644 --- a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts +++ b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts @@ -141,13 +141,8 @@ export class ThemeRegistry { previousIds[theme.id] = theme; } this.extensionThemes.length = 0; - for (let ext of extensions) { - let extensionData: ExtensionData = { - extensionId: ext.description.identifier.value, - extensionPublisher: ext.description.publisher, - extensionName: ext.description.name, - extensionIsBuiltin: ext.description.isBuiltin - }; + for (const ext of extensions) { + const extensionData = ExtensionData.fromName(ext.description.publisher, ext.description.name, ext.description.isBuiltin); this.onThemes(extensionData, ext.description.extensionLocation, ext.value, this.extensionThemes, ext.collector); } for (const theme of this.extensionThemes) { diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index b575fbeb048c6..502d0fbd8a161 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -71,18 +71,19 @@ export interface IWorkbenchThemeService extends IThemeService { setColorTheme(themeId: string | undefined | IWorkbenchColorTheme, settingsTarget: ThemeSettingTarget): Promise; getColorTheme(): IWorkbenchColorTheme; getColorThemes(): Promise; - getMarketplaceColorThemes(id: string, version: string): Promise; + getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise; onDidColorThemeChange: Event; - restoreColorTheme(): void; - setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ThemeSettingTarget): Promise; + setFileIconTheme(iconThemeId: string | undefined | IWorkbenchFileIconTheme, settingsTarget: ThemeSettingTarget): Promise; getFileIconTheme(): IWorkbenchFileIconTheme; getFileIconThemes(): Promise; + getMarketplaceFileIconThemes(publisher: string, name: string, version: string): Promise; onDidFileIconThemeChange: Event; - setProductIconTheme(iconThemeId: string | undefined, settingsTarget: ThemeSettingTarget): Promise; + setProductIconTheme(iconThemeId: string | undefined | IWorkbenchProductIconTheme, settingsTarget: ThemeSettingTarget): Promise; getProductIconTheme(): IWorkbenchProductIconTheme; getProductIconThemes(): Promise; + getMarketplaceProductIconThemes(publisher: string, name: string, version: string): Promise; onDidProductIconThemeChange: Event; } @@ -199,6 +200,9 @@ export namespace ExtensionData { } return undefined; } + export function fromName(publisher: string, name: string, isBuiltin = false) : ExtensionData { + return { extensionPublisher: publisher, extensionId: `${publisher}.${name}`, extensionName: name, extensionIsBuiltin: isBuiltin }; + } } export interface IThemeExtensionPoint { From 118d34cbe7c241bd87b17fc936412a8baffdbf88 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 25 Nov 2021 20:04:16 +0100 Subject: [PATCH 0050/2210] add browse marketplace for file and product icons (for #137289) --- .../themes/browser/themes.contribution.ts | 539 +++++++++--------- 1 file changed, 266 insertions(+), 273 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index aa2fc7bb01514..047472ad3e6c9 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -9,7 +9,7 @@ import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions'; -import { IWorkbenchThemeService, IWorkbenchTheme, ThemeSettingTarget, IWorkbenchColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchTheme, ThemeSettingTarget, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry'; @@ -31,209 +31,11 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.')); -export class SelectColorThemeAction extends Action { - - static readonly ID = 'workbench.action.selectTheme'; - static readonly LABEL = localize('selectTheme.label', "Color Theme"); - - static readonly INSTALL_ADDITIONAL = localize('installColorThemes', "Install Additional Color Themes..."); - static readonly BROWSE_ADDITIONAL = '$(plus) ' + localize('browseColorThemes', "Browse Additional Color Themes..."); - - - constructor( - id: string, - label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, - @ILogService private readonly logService: ILogService, - @IProgressService private progressService: IProgressService - ) { - super(id, label); - } - - override run(): Promise { - return this.themeService.getColorThemes().then(themes => { - const currentTheme = this.themeService.getColorTheme(); - - const supportsGallery = this.extensionGalleryService.isEnabled(); - const supportsGalleryPreview = supportsGallery && this.extensionResourceLoaderService.supportsExtensionGalleryResources; - - const picks: QuickPickInput[] = [ - ...(supportsGalleryPreview ? configurationEntries(SelectColorThemeAction.BROWSE_ADDITIONAL) : []), - ...toEntries(themes.filter(t => t.type === ColorScheme.LIGHT), localize('themes.category.light', "light themes")), - ...toEntries(themes.filter(t => t.type === ColorScheme.DARK), localize('themes.category.dark', "dark themes")), - ...toEntries(themes.filter(t => t.type === ColorScheme.HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")), - ...(supportsGallery && !supportsGalleryPreview ? configurationEntries(SelectColorThemeAction.INSTALL_ADDITIONAL) : []), - ]; - - let selectThemeTimeout: number | undefined; - - const selectTheme = (theme: IWorkbenchTheme | undefined, applyTheme: boolean) => { - if (selectThemeTimeout) { - clearTimeout(selectThemeTimeout); - } - selectThemeTimeout = window.setTimeout(() => { - selectThemeTimeout = undefined; - const newTheme = (theme ?? currentTheme) as IWorkbenchColorTheme; - this.themeService.setColorTheme(newTheme, applyTheme ? 'auto' : 'preview').then(undefined, - err => { - onUnexpectedError(err); - this.themeService.setColorTheme(currentTheme.id, undefined); - } - ); - }, applyTheme ? 0 : 200); - }; - - return new Promise((s, _) => { - const browseInstalledThemes = (activeItemId: string | undefined) => { - let isCompleted = false; - - const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === activeItemId); - const quickpick = this.quickInputService.createQuickPick(); - quickpick.items = picks; - quickpick.sortByLabel = false; - quickpick.matchOnDescription = true; - quickpick.placeholder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); - quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; - quickpick.canSelectMany = false; - quickpick.onDidAccept(async _ => { - const themeItem = quickpick.activeItems[0]; - if (!themeItem || themeItem.theme === undefined) { // 'pick in marketplace' entry - if (supportsGalleryPreview) { - browseMarketplaceThemes(quickpick.value); - } else { - openExtensionViewlet(this.paneCompositeService, `category:themes ${quickpick.value}`); - } - } else { - let themeToSet = themeItem.theme; - selectTheme(themeToSet, true); - } - isCompleted = true; - quickpick.hide(); - s(); - }); - quickpick.onDidTriggerItemButton(e => { - if (isItem(e.item)) { - const extensionId = e.item.theme?.extensionData?.extensionId; - if (extensionId) { - openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`); - } else { - openExtensionViewlet(this.paneCompositeService, `category:themes ${quickpick.value}`); - } - } - }); - - quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false)); - quickpick.onDidHide(() => { - if (!isCompleted) { - selectTheme(currentTheme, true); - s(); - isCompleted = true; - } - }); - quickpick.show(); - }; - - const browseMarketplaceThemes = (value: string) => { - let isCompleted = false; - const marketplaceThemes = new MarketplaceThemes(this.extensionGalleryService, this.extensionManagementService, this.themeService, this.logService); - - const mp_quickpick = this.quickInputService.createQuickPick(); - mp_quickpick.items = []; - mp_quickpick.sortByLabel = false; - mp_quickpick.matchOnDescription = true; - mp_quickpick.buttons = [this.quickInputService.backButton]; - mp_quickpick.title = 'Marketplace Themes'; - mp_quickpick.placeholder = localize('themes.selectMarketplaceTheme', "Type to Search More. Select to Install. Up/Down Keys to Preview"); - mp_quickpick.canSelectMany = false; - mp_quickpick.onDidChangeValue(() => marketplaceThemes.trigger(mp_quickpick.value)); - mp_quickpick.onDidAccept(async _ => { - let themeItem = mp_quickpick.activeItems[0]; - if (themeItem?.galleryExtension) { - isCompleted = true; - mp_quickpick.hide(); - const success = await this.installExtension(themeItem.galleryExtension); - if (success) { - selectTheme(themeItem.theme, true); - } - s(); - } - }); - - mp_quickpick.onDidTriggerItemButton(e => { - if (isItem(e.item)) { - const extensionId = e.item.theme?.extensionData?.extensionId; - if (extensionId) { - openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`); - } else { - openExtensionViewlet(this.paneCompositeService, `category:themes ${mp_quickpick.value}`); - } - } - }); - mp_quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false)); - - mp_quickpick.onDidHide(() => { - if (!isCompleted) { - selectTheme(currentTheme, true); - isCompleted = true; - } - marketplaceThemes.dispose(); - }); - - mp_quickpick.onDidTriggerButton(e => { - if (e === this.quickInputService.backButton) { - mp_quickpick.hide(); - browseInstalledThemes(undefined); - } - }); - - marketplaceThemes.onDidChange(() => { - let items = marketplaceThemes.themes; - if (marketplaceThemes.isSearching) { - items = items.concat({ label: '$(sync~spin) Searching for themes...', id: undefined, alwaysShow: true }); - } - const activeItemId = mp_quickpick.activeItems[0]?.id; - const newActiveItem = activeItemId ? items.find(i => isItem(i) && i.id === activeItemId) : undefined; - - mp_quickpick.items = items; - if (newActiveItem) { - mp_quickpick.activeItems = [newActiveItem as ThemeItem]; - } - }); - marketplaceThemes.trigger(value); - mp_quickpick.show(); - }; - - browseInstalledThemes(currentTheme.id); - }); - - }); - } - - private async installExtension(galleryExtension: IGalleryExtension) { - try { - openExtensionViewlet(this.paneCompositeService, `@id:${galleryExtension.identifier.id}`); - await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('installing extensions', "Installing Extension {0}...", galleryExtension.displayName) - }, async () => { - await this.extensionManagementService.installFromGallery(galleryExtension); - }); - return true; - } catch (e) { - this.logService.error(`Problem installing extension ${galleryExtension.identifier.id}`, e); - return false; - } - } - -} +type PickerResult = 'back' | 'selected' | 'cancelled'; class MarketplaceThemes { private readonly _installedExtensions: Promise>; @@ -247,10 +49,15 @@ class MarketplaceThemes { private readonly _queryDelayer = new ThrottledDelayer(200); constructor( - private extensionGalleryService: IExtensionGalleryService, - extensionManagementService: IExtensionManagementService, - private themeService: IWorkbenchThemeService, - private logService: ILogService + private readonly getMarketplaceColorThemes: (publisher: string, name: string, version: string) => Promise, + private readonly marketplaceQuery: string, + + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @ILogService private readonly logService: ILogService, + @IProgressService private readonly progressService: IProgressService, + @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService ) { this._installedExtensions = extensionManagementService.getInstalled().then(installed => { const result = new Set(); @@ -290,7 +97,7 @@ class MarketplaceThemes { try { const installedExtensions = await this._installedExtensions; - const options = { text: `category:themes ${value}`, pageSize: 10 }; + const options = { text: `${this.marketplaceQuery} ${value}`, pageSize: 20 }; const pager = await this.extensionGalleryService.query(options, token); for (let i = 0; i < pager.total && i < 2; i++) { if (token.isCancellationRequested) { @@ -307,7 +114,7 @@ class MarketplaceThemes { const ext = gallery[i]; if (!installedExtensions.has(ext.identifier.id) && !this._marketplaceExtensions.has(ext.identifier.id)) { this._marketplaceExtensions.add(ext.identifier.id); - const themes = await this.themeService.getMarketplaceColorThemes(ext.publisher, ext.name, ext.version); + const themes = await this.getMarketplaceColorThemes(ext.publisher, ext.name, ext.version); for (const theme of themes) { this._marketplaceThemes.push({ id: theme.id, theme: theme, label: theme.label, description: `${ext.displayName} · ${ext.publisherDisplayName}`, galleryExtension: ext, buttons: [configureButton] }); } @@ -327,6 +134,94 @@ class MarketplaceThemes { this._onDidChange.fire(); } + public openQuickPick(value: string, currentTheme: IWorkbenchTheme | undefined, selectTheme: (theme: IWorkbenchTheme | undefined, applyTheme: boolean) => void): Promise { + let result: PickerResult | undefined = undefined; + return new Promise((s, _) => { + const quickpick = this.quickInputService.createQuickPick(); + quickpick.items = []; + quickpick.sortByLabel = false; + quickpick.matchOnDescription = true; + quickpick.buttons = [this.quickInputService.backButton]; + quickpick.title = 'Marketplace Themes'; + quickpick.placeholder = localize('themes.selectMarketplaceTheme', "Type to Search More. Select to Install. Up/Down Keys to Preview"); + quickpick.canSelectMany = false; + quickpick.onDidChangeValue(() => this.trigger(quickpick.value)); + quickpick.onDidAccept(async _ => { + let themeItem = quickpick.selectedItems[0]; + if (themeItem?.galleryExtension) { + result = 'selected'; + quickpick.hide(); + const success = await this.installExtension(themeItem.galleryExtension); + if (success) { + selectTheme(themeItem.theme, true); + } + } + }); + + quickpick.onDidTriggerItemButton(e => { + if (isItem(e.item)) { + const extensionId = e.item.theme?.extensionData?.extensionId; + if (extensionId) { + openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`); + } else { + openExtensionViewlet(this.paneCompositeService, `${this.marketplaceQuery} ${quickpick.value}`); + } + } + }); + quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false)); + + quickpick.onDidHide(() => { + if (result === undefined) { + selectTheme(currentTheme, true); + result = 'cancelled'; + + } + quickpick.dispose(); + s(result); + }); + + quickpick.onDidTriggerButton(e => { + if (e === this.quickInputService.backButton) { + result = 'back'; + quickpick.hide(); + } + }); + + this.onDidChange(() => { + let items = this.themes; + if (this.isSearching) { + items = items.concat({ label: '$(sync~spin) Searching for themes...', id: undefined, alwaysShow: true }); + } + const activeItemId = quickpick.activeItems[0]?.id; + const newActiveItem = activeItemId ? items.find(i => isItem(i) && i.id === activeItemId) : undefined; + + quickpick.items = items; + if (newActiveItem) { + quickpick.activeItems = [newActiveItem as ThemeItem]; + } + }); + this.trigger(value); + quickpick.show(); + }); + } + + private async installExtension(galleryExtension: IGalleryExtension) { + try { + openExtensionViewlet(this.paneCompositeService, `@id:${galleryExtension.identifier.id}`); + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('installing extensions', "Installing Extension {0}...", galleryExtension.displayName) + }, async () => { + await this.extensionManagementService.installFromGallery(galleryExtension); + }); + return true; + } catch (e) { + this.logService.error(`Problem installing extension ${galleryExtension.identifier.id}`, e); + return false; + } + } + + public dispose() { if (this._tokenSource) { this._tokenSource.cancel(); @@ -338,79 +233,158 @@ class MarketplaceThemes { } } -abstract class AbstractIconThemeAction extends Action { +abstract class AbstractSelectThemeAction extends Action { constructor( id: string, label: string, private readonly quickInputService: IQuickInputService, private readonly extensionGalleryService: IExtensionGalleryService, - private readonly paneCompositeService: IPaneCompositePartService + private readonly paneCompositeService: IPaneCompositePartService, + private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, + private readonly instantiationService: IInstantiationService ) { super(id, label); } - protected abstract get builtInEntry(): QuickPickInput; protected abstract get installMessage(): string; + protected abstract get browseMessage(): string; protected abstract get placeholderMessage(): string; protected abstract get marketplaceTag(): string; - protected abstract setTheme(id: string, settingsTarget: ThemeSettingTarget): Promise; + protected abstract setTheme(theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget): Promise; + protected abstract getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise; - protected pick(themes: IWorkbenchTheme[], currentTheme: IWorkbenchTheme) { - let picks: QuickPickInput[] = [this.builtInEntry, ...toEntries(themes), ...(this.extensionGalleryService.isEnabled() ? configurationEntries(this.installMessage) : [])]; + protected async pick(picks: QuickPickInput[], currentTheme: IWorkbenchTheme) { + let marketplaceThemes: MarketplaceThemes | undefined; + if (this.extensionGalleryService.isEnabled()) { + if (this.extensionResourceLoaderService.supportsExtensionGalleryResources) { + marketplaceThemes = this.instantiationService.createInstance(MarketplaceThemes, this.getMarketplaceColorThemes.bind(this), this.marketplaceTag); + picks = [...configurationEntries(this.browseMessage), ...picks]; + } else { + picks = [...picks, ...configurationEntries(this.installMessage)]; + } + } let selectThemeTimeout: number | undefined; - const selectTheme = (theme: ThemeItem, applyTheme: boolean) => { + const selectTheme = (theme: ThemeItem | undefined, applyTheme: boolean) => { if (selectThemeTimeout) { clearTimeout(selectThemeTimeout); } selectThemeTimeout = window.setTimeout(() => { selectThemeTimeout = undefined; - const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; - this.setTheme(themeId, applyTheme ? 'auto' : 'preview').then(undefined, + const newTheme = (theme ?? currentTheme) as IWorkbenchTheme; + this.setTheme(newTheme, applyTheme ? 'auto' : 'preview').then(undefined, err => { onUnexpectedError(err); - this.setTheme(currentTheme.id, undefined); + this.setTheme(currentTheme, undefined); } ); }, applyTheme ? 0 : 200); }; - return new Promise((s, _) => { - let isCompleted = false; + const pickInstalledThemes = (activeItemId: string | undefined) => { + return new Promise((s, _) => { + let isCompleted = false; + + const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === activeItemId); + const quickpick = this.quickInputService.createQuickPick(); + quickpick.items = picks; + quickpick.placeholder = this.placeholderMessage; + quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; + quickpick.canSelectMany = false; + quickpick.onDidAccept(async _ => { + isCompleted = true; + const theme = quickpick.selectedItems[0]; + if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry + if (marketplaceThemes) { + const res = await marketplaceThemes.openQuickPick(quickpick.value, currentTheme, selectTheme); + if (res === 'back') { + await pickInstalledThemes(undefined); + } + } else { + openExtensionViewlet(this.paneCompositeService, `${this.marketplaceTag} ${quickpick.value}`); + } + } else { + selectTheme(theme, true); + } - const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === currentTheme.id); - const quickpick = this.quickInputService.createQuickPick(); - quickpick.items = picks; - quickpick.placeholder = this.placeholderMessage; - quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; - quickpick.canSelectMany = false; - quickpick.onDidAccept(_ => { - const theme = quickpick.activeItems[0]; - if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry - openExtensionViewlet(this.paneCompositeService, `${this.marketplaceTag} ${quickpick.value}`); - } else { - selectTheme(theme, true); - } - isCompleted = true; - quickpick.hide(); - s(); - }); - quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); - quickpick.onDidHide(() => { - if (!isCompleted) { - selectTheme(currentTheme, true); + quickpick.hide(); s(); - } + }); + quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); + quickpick.onDidHide(() => { + if (!isCompleted) { + selectTheme(currentTheme, true); + s(); + } + quickpick.dispose(); + }); + quickpick.onDidTriggerItemButton(e => { + if (isItem(e.item)) { + const extensionId = e.item.theme?.extensionData?.extensionId; + if (extensionId) { + openExtensionViewlet(this.paneCompositeService, `@id:${extensionId}`); + } else { + openExtensionViewlet(this.paneCompositeService, `${this.marketplaceTag} ${quickpick.value}`); + } + } + }); + quickpick.show(); }); - quickpick.show(); - }); + }; + await pickInstalledThemes(currentTheme.id); + + marketplaceThemes?.dispose(); + } } -class SelectFileIconThemeAction extends AbstractIconThemeAction { +export class SelectColorThemeAction extends AbstractSelectThemeAction { + + static readonly ID = 'workbench.action.selectTheme'; + static readonly LABEL = localize('selectTheme.label', "Color Theme"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @IPaneCompositePartService paneCompositeService: IPaneCompositePartService, + @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, + @IInstantiationService instantiationService: IInstantiationService + + ) { + super(id, label, quickInputService, extensionGalleryService, paneCompositeService, extensionResourceLoaderService, instantiationService); + } + + protected override readonly installMessage = localize('installColorThemes', "Install Additional Color Themes..."); + protected override readonly browseMessage = '$(plus) ' + localize('browseColorThemes', "Browse Additional Color Themes..."); + protected override readonly placeholderMessage = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); + protected override readonly marketplaceTag = 'category:themes'; + protected override setTheme(theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget): Promise { + return this.themeService.setColorTheme(theme as IWorkbenchColorTheme, settingsTarget); + } + protected override getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise { + return this.themeService.getMarketplaceColorThemes(publisher, name, version); + } + + override async run(): Promise { + const themes = await this.themeService.getColorThemes(); + const currentTheme = this.themeService.getColorTheme(); + + const picks: QuickPickInput[] = [ + ...toEntries(themes.filter(t => t.type === ColorScheme.LIGHT), localize('themes.category.light', "light themes")), + ...toEntries(themes.filter(t => t.type === ColorScheme.DARK), localize('themes.category.dark', "dark themes")), + ...toEntries(themes.filter(t => t.type === ColorScheme.HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")), + ]; + this.pick(picks, currentTheme); + } +} + +class SelectFileIconThemeAction extends AbstractSelectThemeAction { static readonly ID = 'workbench.action.selectIconTheme'; static readonly LABEL = localize('selectIconTheme.label', "File Icon Theme"); @@ -421,27 +395,37 @@ class SelectFileIconThemeAction extends AbstractIconThemeAction { @IQuickInputService quickInputService: IQuickInputService, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, - @IPaneCompositePartService paneCompositeService: IPaneCompositePartService - + @IPaneCompositePartService paneCompositeService: IPaneCompositePartService, + @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, + @IInstantiationService instantiationService: IInstantiationService ) { - super(id, label, quickInputService, extensionGalleryService, paneCompositeService); + super(id, label, quickInputService, extensionGalleryService, paneCompositeService, extensionResourceLoaderService, instantiationService); } - protected builtInEntry: QuickPickInput = { id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable File Icons') }; - protected installMessage = localize('installIconThemes', "Install Additional File Icon Themes..."); - protected placeholderMessage = localize('themes.selectIconTheme', "Select File Icon Theme"); - protected marketplaceTag = 'tag:icon-theme'; - protected setTheme(id: string, settingsTarget: ThemeSettingTarget) { - return this.themeService.setFileIconTheme(id, settingsTarget); + protected override readonly installMessage = localize('installIconThemes', "Install Additional File Icon Themes..."); + protected override readonly browseMessage = localize('browseIconThemes', "$(plus) Browse Additional File Icon Themes..."); + protected override readonly placeholderMessage = localize('themes.selectIconTheme', "Select File Icon Theme"); + protected override readonly marketplaceTag = 'tag:icon-theme'; + protected override setTheme(theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) { + return this.themeService.setFileIconTheme(theme as IWorkbenchFileIconTheme, settingsTarget); + } + protected override getMarketplaceColorThemes(publisher: string, name: string, version: string) { + return this.themeService.getMarketplaceFileIconThemes(publisher, name, version); } override async run(): Promise { - this.pick(await this.themeService.getFileIconThemes(), this.themeService.getFileIconTheme()); + const picks: QuickPickInput[] = [ + { type: 'separator', label: localize('fileIconThemeCategory', 'file icon themes') }, + { id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable File Icons') }, + ...toEntries(await this.themeService.getFileIconThemes()), + ]; + + await this.pick(picks, this.themeService.getFileIconTheme()); } } -class SelectProductIconThemeAction extends AbstractIconThemeAction { +class SelectProductIconThemeAction extends AbstractSelectThemeAction { static readonly ID = 'workbench.action.selectProductIconTheme'; static readonly LABEL = localize('selectProductIconTheme.label', "Product Icon Theme"); @@ -452,30 +436,39 @@ class SelectProductIconThemeAction extends AbstractIconThemeAction { @IQuickInputService quickInputService: IQuickInputService, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, - @IPaneCompositePartService paneCompositeService: IPaneCompositePartService + @IPaneCompositePartService paneCompositeService: IPaneCompositePartService, + @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, + @IInstantiationService instantiationService: IInstantiationService ) { - super(id, label, quickInputService, extensionGalleryService, paneCompositeService); + super(id, label, quickInputService, extensionGalleryService, paneCompositeService, extensionResourceLoaderService, instantiationService); } - protected builtInEntry: QuickPickInput = { id: DEFAULT_PRODUCT_ICON_THEME_ID, label: localize('defaultProductIconThemeLabel', 'Default') }; - protected installMessage = localize('installProductIconThemes', "Install Additional Product Icon Themes..."); - protected placeholderMessage = localize('themes.selectProductIconTheme', "Select Product Icon Theme"); - protected marketplaceTag = 'tag:product-icon-theme'; - protected setTheme(id: string, settingsTarget: ThemeSettingTarget) { - return this.themeService.setProductIconTheme(id, settingsTarget); + protected override readonly installMessage = localize('installProductIconThemes', "Install Additional Product Icon Themes..."); + protected override readonly browseMessage = localize('browseProductIconThemes', "$(plus) Browse Additional Product Icon Themes..."); + protected override readonly placeholderMessage = localize('themes.selectProductIconTheme', "Select Product Icon Theme"); + protected override readonly marketplaceTag = 'tag:product-icon-theme'; + protected override setTheme(theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget): Promise { + return this.themeService.setProductIconTheme(theme as IWorkbenchProductIconTheme, settingsTarget); + } + protected override getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise { + return this.themeService.getMarketplaceProductIconThemes(publisher, name, version); } - override async run(): Promise { - this.pick(await this.themeService.getProductIconThemes(), this.themeService.getProductIconTheme()); + const picks: QuickPickInput[] = [ + { type: 'separator', label: localize('productIconThemeCategory', 'product icon themes') }, + { id: DEFAULT_PRODUCT_ICON_THEME_ID, label: localize('defaultProductIconThemeLabel', 'Default') }, + ...toEntries(await this.themeService.getProductIconThemes()), + ]; + + await this.pick(picks, this.themeService.getProductIconTheme()); } } function configurationEntries(label: string): QuickPickInput[] { return [ { - type: 'separator', - label: 'marketplace themes' + type: 'separator' }, { id: undefined, @@ -496,12 +489,12 @@ function openExtensionViewlet(paneCompositeService: IPaneCompositePartService, q }); } interface ThemeItem extends IQuickPickItem { - id: string | undefined; - theme?: IWorkbenchTheme; - galleryExtension?: IGalleryExtension; - label: string; - description?: string; - alwaysShow?: boolean; + readonly id: string | undefined; + readonly theme?: IWorkbenchTheme; + readonly galleryExtension?: IGalleryExtension; + readonly label: string; + readonly description?: string; + readonly alwaysShow?: boolean; } function isItem(i: QuickPickInput): i is ThemeItem { From 3fb9624b29ef3878abef01c3a817a9b489b9c1fc Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 21:07:14 +0100 Subject: [PATCH 0051/2210] Remove `KeepAlive` message and rely solely on unacknowledged messages as a trigger for timeout --- src/vs/base/parts/ipc/common/ipc.net.ts | 102 +++++------------- .../base/parts/ipc/test/node/ipc.net.test.ts | 4 +- .../remote/common/remoteAgentConnection.ts | 15 ++- .../remote/common/remote.contribution.ts | 41 +++++++ 4 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 5ff8d7bcf4025..6b72f2f2dadac 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -253,7 +253,6 @@ const enum ProtocolMessageType { Regular = 1, Control = 2, Ack = 3, - KeepAlive = 4, Disconnect = 5, ReplayRequest = 6 } @@ -264,7 +263,6 @@ function protocolMessageTypeToString(messageType: ProtocolMessageType) { case ProtocolMessageType.Regular: return 'Regular'; case ProtocolMessageType.Control: return 'Control'; case ProtocolMessageType.Ack: return 'Ack'; - case ProtocolMessageType.KeepAlive: return 'KeepAlive'; case ProtocolMessageType.Disconnect: return 'Disconnect'; case ProtocolMessageType.ReplayRequest: return 'ReplayRequest'; } @@ -277,17 +275,11 @@ export const enum ProtocolConstants { */ AcknowledgeTime = 2000, // 2 seconds /** - * If there is a message that has been unacknowledged for 10 seconds, consider the connection closed... + * If there is a sent message that has been unacknowledged for 20 seconds, + * and we didn't see any incoming server data in the past 20 seconds, + * then consider the connection has timed out. */ - AcknowledgeTimeoutTime = 20000, // 20 seconds - /** - * Send at least a message every 5s for keep alive reasons. - */ - KeepAliveTime = 5000, // 5 seconds - /** - * If there is no message received for 10 seconds, consider the connection closed... - */ - KeepAliveTimeoutTime = 20000, // 20 seconds + TimeoutTime = 20000, // 20 seconds /** * If there is no reconnection within this time-frame, consider the connection permanently closed... */ @@ -406,6 +398,7 @@ class ProtocolReader extends Disposable { class ProtocolWriter { private _isDisposed: boolean; + private _isPaused: boolean; private readonly _socket: ISocket; private _data: VSBuffer[]; private _totalLength: number; @@ -413,6 +406,7 @@ class ProtocolWriter { constructor(socket: ISocket) { this._isDisposed = false; + this._isPaused = false; this._socket = socket; this._data = []; this._totalLength = 0; @@ -438,6 +432,10 @@ class ProtocolWriter { this._writeNow(); } + public pause() { + this._isPaused = true; + } + public write(msg: ProtocolMessage) { if (this._isDisposed) { // ignore: there could be left-over promises which complete and then @@ -484,6 +482,9 @@ class ProtocolWriter { if (this._totalLength === 0) { return; } + if (this._isPaused) { + return; + } const data = this._bufferTake(); this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolWrite, { byteLength: data.byteLength }); this._socket.write(data); @@ -758,9 +759,6 @@ export class PersistentProtocol implements IMessagePassingProtocol { private _incomingMsgLastTime: number; private _incomingAckTimeout: any | null; - private _outgoingKeepAliveTimeout: any | null; - private _incomingKeepAliveTimeout: any | null; - private _lastReplayRequestTime: number; private _socket: ISocket; @@ -802,9 +800,6 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._incomingMsgLastTime = 0; this._incomingAckTimeout = null; - this._outgoingKeepAliveTimeout = null; - this._incomingKeepAliveTimeout = null; - this._lastReplayRequestTime = 0; this._socketDisposables = []; @@ -818,9 +813,6 @@ export class PersistentProtocol implements IMessagePassingProtocol { if (initialChunk) { this._socketReader.acceptChunk(initialChunk); } - - this._sendKeepAliveCheck(); - this._recvKeepAliveCheck(); } dispose(): void { @@ -832,14 +824,6 @@ export class PersistentProtocol implements IMessagePassingProtocol { clearTimeout(this._incomingAckTimeout); this._incomingAckTimeout = null; } - if (this._outgoingKeepAliveTimeout) { - clearTimeout(this._outgoingKeepAliveTimeout); - this._outgoingKeepAliveTimeout = null; - } - if (this._incomingKeepAliveTimeout) { - clearTimeout(this._incomingKeepAliveTimeout); - this._incomingKeepAliveTimeout = null; - } this._socketDisposables = dispose(this._socketDisposables); } @@ -853,50 +837,8 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketWriter.flush(); } - private _sendKeepAliveCheck(): void { - if (this._outgoingKeepAliveTimeout) { - // there will be a check in the near future - return; - } - - const timeSinceLastOutgoingMsg = Date.now() - this._socketWriter.lastWriteTime; - if (timeSinceLastOutgoingMsg >= ProtocolConstants.KeepAliveTime) { - // sufficient time has passed since last message was written, - // and no message from our side needed to be sent in the meantime, - // so we will send a message containing only a keep alive. - const msg = new ProtocolMessage(ProtocolMessageType.KeepAlive, 0, 0, getEmptyBuffer()); - this._socketWriter.write(msg); - this._sendKeepAliveCheck(); - return; - } - - this._outgoingKeepAliveTimeout = setTimeout(() => { - this._outgoingKeepAliveTimeout = null; - this._sendKeepAliveCheck(); - }, ProtocolConstants.KeepAliveTime - timeSinceLastOutgoingMsg + 5); - } - - private _recvKeepAliveCheck(): void { - if (this._incomingKeepAliveTimeout) { - // there will be a check in the near future - return; - } - - const timeSinceLastIncomingMsg = Date.now() - this._socketReader.lastReadTime; - if (timeSinceLastIncomingMsg >= ProtocolConstants.KeepAliveTimeoutTime) { - // It's been a long time since we received a server message - // But this might be caused by the event loop being busy and failing to read messages - if (!this._loadEstimator.hasHighLoad()) { - // Trash the socket - this._onSocketTimeout.fire(undefined); - return; - } - } - - this._incomingKeepAliveTimeout = setTimeout(() => { - this._incomingKeepAliveTimeout = null; - this._recvKeepAliveCheck(); - }, Math.max(ProtocolConstants.KeepAliveTimeoutTime - timeSinceLastIncomingMsg, 0) + 5); + pauseSocketWriting() { + this._socketWriter.pause(); } public getSocket(): ISocket { @@ -937,9 +879,6 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketWriter.write(toSend[i]); } this._recvAckCheck(); - - this._sendKeepAliveCheck(); - this._recvKeepAliveCheck(); } public acceptDisconnect(): void { @@ -1064,8 +1003,15 @@ export class PersistentProtocol implements IMessagePassingProtocol { const oldestUnacknowledgedMsg = this._outgoingUnackMsg.peek()!; const timeSinceOldestUnacknowledgedMsg = Date.now() - oldestUnacknowledgedMsg.writtenTime; - if (timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.AcknowledgeTimeoutTime) { + const timeSinceLastReceivedSomeData = Date.now() - this._socketReader.lastReadTime; + + if ( + timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.TimeoutTime + && timeSinceLastReceivedSomeData >= ProtocolConstants.TimeoutTime + ) { // It's been a long time since our sent message was acknowledged + // and a long time since we received some data + // But this might be caused by the event loop being busy and failing to read messages if (!this._loadEstimator.hasHighLoad()) { // Trash the socket @@ -1077,7 +1023,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._outgoingAckTimeout = setTimeout(() => { this._outgoingAckTimeout = null; this._recvAckCheck(); - }, Math.max(ProtocolConstants.AcknowledgeTimeoutTime - timeSinceOldestUnacknowledgedMsg, 0) + 5); + }, Math.max(ProtocolConstants.TimeoutTime - timeSinceOldestUnacknowledgedMsg, 500)); } private _sendAck(): void { diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 042bfcbabfef5..b8f3c3d2ebe8c 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -342,7 +342,7 @@ suite('PersistentProtocol reconnection', () => { assert.strictEqual(b.unacknowledgedCount, 1); // wait for scheduled _recvAckCheck() to execute - await timeout(2 * ProtocolConstants.AcknowledgeTimeoutTime); + await timeout(2 * ProtocolConstants.TimeoutTime); assert.strictEqual(a.unacknowledgedCount, 1); assert.strictEqual(b.unacknowledgedCount, 1); @@ -351,7 +351,7 @@ suite('PersistentProtocol reconnection', () => { a.endAcceptReconnection(); assert.strictEqual(timeoutListenerCalled, false); - await timeout(2 * ProtocolConstants.AcknowledgeTimeoutTime); + await timeout(2 * ProtocolConstants.TimeoutTime); assert.strictEqual(a.unacknowledgedCount, 0); assert.strictEqual(b.unacknowledgedCount, 0); assert.strictEqual(timeoutListenerCalled, false); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index b555abd36942a..7729501248f05 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -512,7 +512,7 @@ export class ReconnectionPermanentFailureEvent { } export type PersistentConnectionEvent = ConnectionGainEvent | ConnectionLostEvent | ReconnectionWaitEvent | ReconnectionRunningEvent | ReconnectionPermanentFailureEvent; -abstract class PersistentConnection extends Disposable { +export abstract class PersistentConnection extends Disposable { public static triggerPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void { this._permanentFailure = true; @@ -521,6 +521,15 @@ abstract class PersistentConnection extends Disposable { this._permanentFailureHandled = handled; this._instances.forEach(instance => instance._gotoPermanentFailure(this._permanentFailureMillisSinceLastIncomingData, this._permanentFailureAttempt, this._permanentFailureHandled)); } + + public static debugTriggerReconnection() { + this._instances.forEach(instance => instance._beginReconnecting()); + } + + public static debugPauseSocketWriting() { + this._instances.forEach(instance => instance._pauseSocketWriting()); + } + private static _permanentFailure: boolean = false; private static _permanentFailureMillisSinceLastIncomingData: number = 0; private static _permanentFailureAttempt: number = 0; @@ -678,6 +687,10 @@ abstract class PersistentConnection extends Disposable { safeDisposeProtocolAndSocket(this.protocol); } + private _pauseSocketWriting(): void { + this.protocol.pauseSocketWriting(); + } + protected abstract _reconnect(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise; } diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index a75b4c1198ce5..ad750d87ffd2c 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -25,6 +25,10 @@ import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/d import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { firstOrDefault } from 'vs/base/common/arrays'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { CATEGORIES } from 'vs/workbench/common/actions'; +import { PersistentConnection } from 'vs/platform/remote/common/remoteAgentConnection'; export class LabelContribution implements IWorkbenchContribution { constructor( @@ -161,6 +165,43 @@ workbenchContributionsRegistry.registerWorkbenchContribution(RemoteLogOutputChan workbenchContributionsRegistry.registerWorkbenchContribution(TunnelFactoryContribution, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(ShowCandidateContribution, LifecyclePhase.Ready); +const enableDiagnostics = false; + +if (enableDiagnostics) { + class TriggerReconnectAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.triggerReconnect', + title: { value: localize('triggerReconnect', "Connection: Trigger Reconnect"), original: 'Connection: Trigger Reconnect' }, + category: CATEGORIES.Developer, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + PersistentConnection.debugTriggerReconnection(); + } + } + + class PauseSocketWriting extends Action2 { + constructor() { + super({ + id: 'workbench.action.pauseSocketWriting', + title: { value: localize('pauseSocketWriting', "Connection: Pause socket writing"), original: 'Connection: Pause socket writing' }, + category: CATEGORIES.Developer, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + PersistentConnection.debugPauseSocketWriting(); + } + } + + registerAction2(TriggerReconnectAction); + registerAction2(PauseSocketWriting); +} + const extensionKindSchema: IJSONSchema = { type: 'string', enum: [ From 1c1df5532da41716a1bf8252e73fcbba6395e48c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 25 Nov 2021 21:45:32 +0100 Subject: [PATCH 0052/2210] themes actions: convert to Action2 --- .../themes/browser/themes.contribution.ts | 287 ++++++++---------- 1 file changed, 129 insertions(+), 158 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 047472ad3e6c9..68f6570af9d54 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions'; +import { CATEGORIES } from 'vs/workbench/common/actions'; import { IWorkbenchThemeService, IWorkbenchTheme, ThemeSettingTarget, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -31,13 +30,14 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.')); type PickerResult = 'back' | 'selected' | 'cancelled'; -class MarketplaceThemes { +class MarketplaceThemesPicker { private readonly _installedExtensions: Promise>; private readonly _marketplaceExtensions: Set = new Set(); private readonly _marketplaceThemes: ThemeItem[] = []; @@ -129,9 +129,11 @@ class MarketplaceThemes { if (!isPromiseCanceledError(e)) { this.logService.error(`Error while searching for themes:`, e); } + } finally { + this._searchOngoing = false; + this._onDidChange.fire(); } - this._searchOngoing = false; - this._onDidChange.fire(); + } public openQuickPick(value: string, currentTheme: IWorkbenchTheme | undefined, selectTheme: (theme: IWorkbenchTheme | undefined, applyTheme: boolean) => void): Promise { @@ -233,33 +235,27 @@ class MarketplaceThemes { } } -abstract class AbstractSelectThemeAction extends Action { +class InstalledThemesPicker { constructor( - id: string, - label: string, - private readonly quickInputService: IQuickInputService, - private readonly extensionGalleryService: IExtensionGalleryService, - private readonly paneCompositeService: IPaneCompositePartService, - private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, - private readonly instantiationService: IInstantiationService - + private readonly installMessage: string, + private readonly browseMessage: string, + private readonly placeholderMessage: string, + private readonly marketplaceTag: string, + private readonly setTheme: (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => Promise, + private readonly getMarketplaceColorThemes: (publisher: string, name: string, version: string) => Promise, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, + @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super(id, label); } - protected abstract get installMessage(): string; - protected abstract get browseMessage(): string; - protected abstract get placeholderMessage(): string; - protected abstract get marketplaceTag(): string; - - protected abstract setTheme(theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget): Promise; - protected abstract getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise; - - protected async pick(picks: QuickPickInput[], currentTheme: IWorkbenchTheme) { - let marketplaceThemes: MarketplaceThemes | undefined; + public async openQuickPick(picks: QuickPickInput[], currentTheme: IWorkbenchTheme) { + let marketplaceThemes: MarketplaceThemesPicker | undefined; if (this.extensionGalleryService.isEnabled()) { if (this.extensionResourceLoaderService.supportsExtensionGalleryResources) { - marketplaceThemes = this.instantiationService.createInstance(MarketplaceThemes, this.getMarketplaceColorThemes.bind(this), this.marketplaceTag); + marketplaceThemes = this.instantiationService.createInstance(MarketplaceThemesPicker, this.getMarketplaceColorThemes.bind(this), this.marketplaceTag); picks = [...configurationEntries(this.browseMessage), ...picks]; } else { picks = [...picks, ...configurationEntries(this.installMessage)]; @@ -341,129 +337,119 @@ abstract class AbstractSelectThemeAction extends Action { } } -export class SelectColorThemeAction extends AbstractSelectThemeAction { - - static readonly ID = 'workbench.action.selectTheme'; - static readonly LABEL = localize('selectTheme.label', "Color Theme"); +const SelectColorThemeCommandId = 'workbench.action.selectTheme'; - constructor( - id: string, - label: string, - @IQuickInputService quickInputService: IQuickInputService, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, - @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, - @IPaneCompositePartService paneCompositeService: IPaneCompositePartService, - @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, - @IInstantiationService instantiationService: IInstantiationService +registerAction2(class extends Action2 { - ) { - super(id, label, quickInputService, extensionGalleryService, paneCompositeService, extensionResourceLoaderService, instantiationService); + constructor() { + super({ + id: SelectColorThemeCommandId, + title: localize('selectTheme.label', "Color Theme"), + category: CATEGORIES.Preferences, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyT) + } + }); } - protected override readonly installMessage = localize('installColorThemes', "Install Additional Color Themes..."); - protected override readonly browseMessage = '$(plus) ' + localize('browseColorThemes', "Browse Additional Color Themes..."); - protected override readonly placeholderMessage = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); - protected override readonly marketplaceTag = 'category:themes'; - protected override setTheme(theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget): Promise { - return this.themeService.setColorTheme(theme as IWorkbenchColorTheme, settingsTarget); - } - protected override getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise { - return this.themeService.getMarketplaceColorThemes(publisher, name, version); - } + override async run(accessor: ServicesAccessor) { + const themeService = accessor.get(IWorkbenchThemeService); + + const installMessage = localize('installColorThemes', "Install Additional Color Themes..."); + const browseMessage = '$(plus) ' + localize('browseColorThemes', "Browse Additional Color Themes..."); + const placeholderMessage = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); + const marketplaceTag = 'category:themes'; + const setTheme = (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => themeService.setColorTheme(theme as IWorkbenchColorTheme, settingsTarget); + const getMarketplaceColorThemes = (publisher: string, name: string, version: string) => themeService.getMarketplaceColorThemes(publisher, name, version); + + const instantiationService = accessor.get(IInstantiationService); + const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, browseMessage, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes); - override async run(): Promise { - const themes = await this.themeService.getColorThemes(); - const currentTheme = this.themeService.getColorTheme(); + const themes = await themeService.getColorThemes(); + const currentTheme = themeService.getColorTheme(); const picks: QuickPickInput[] = [ ...toEntries(themes.filter(t => t.type === ColorScheme.LIGHT), localize('themes.category.light', "light themes")), ...toEntries(themes.filter(t => t.type === ColorScheme.DARK), localize('themes.category.dark', "dark themes")), ...toEntries(themes.filter(t => t.type === ColorScheme.HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")), ]; - this.pick(picks, currentTheme); + await picker.openQuickPick(picks, currentTheme); } -} +}); -class SelectFileIconThemeAction extends AbstractSelectThemeAction { +const SelectFileIconThemeCommandId = 'workbench.action.selectIconTheme'; - static readonly ID = 'workbench.action.selectIconTheme'; - static readonly LABEL = localize('selectIconTheme.label', "File Icon Theme"); +registerAction2(class extends Action2 { - constructor( - id: string, - label: string, - @IQuickInputService quickInputService: IQuickInputService, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, - @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, - @IPaneCompositePartService paneCompositeService: IPaneCompositePartService, - @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, - @IInstantiationService instantiationService: IInstantiationService - ) { - super(id, label, quickInputService, extensionGalleryService, paneCompositeService, extensionResourceLoaderService, instantiationService); + constructor() { + super({ + id: SelectFileIconThemeCommandId, + title: localize('selectIconTheme.label', "File Icon Theme"), + category: CATEGORIES.Preferences, + f1: true + }); } - protected override readonly installMessage = localize('installIconThemes', "Install Additional File Icon Themes..."); - protected override readonly browseMessage = localize('browseIconThemes', "$(plus) Browse Additional File Icon Themes..."); - protected override readonly placeholderMessage = localize('themes.selectIconTheme', "Select File Icon Theme"); - protected override readonly marketplaceTag = 'tag:icon-theme'; - protected override setTheme(theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) { - return this.themeService.setFileIconTheme(theme as IWorkbenchFileIconTheme, settingsTarget); - } - protected override getMarketplaceColorThemes(publisher: string, name: string, version: string) { - return this.themeService.getMarketplaceFileIconThemes(publisher, name, version); - } + override async run(accessor: ServicesAccessor) { + const themeService = accessor.get(IWorkbenchThemeService); + + const installMessage = localize('installIconThemes', "Install Additional File Icon Themes..."); + const browseMessage = '$(plus) ' + localize('browseIconThemes', "Browse Additional File Icon Themes..."); + const placeholderMessage = localize('themes.selectIconTheme', "Select File Icon Theme (Up/Down Keys to Preview)"); + const marketplaceTag = 'tag:icon-theme'; + const setTheme = (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => themeService.setFileIconTheme(theme as IWorkbenchFileIconTheme, settingsTarget); + const getMarketplaceColorThemes = (publisher: string, name: string, version: string) => themeService.getMarketplaceFileIconThemes(publisher, name, version); + + const instantiationService = accessor.get(IInstantiationService); + const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, browseMessage, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes); - override async run(): Promise { const picks: QuickPickInput[] = [ { type: 'separator', label: localize('fileIconThemeCategory', 'file icon themes') }, { id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable File Icons') }, - ...toEntries(await this.themeService.getFileIconThemes()), + ...toEntries(await themeService.getFileIconThemes()), ]; - await this.pick(picks, this.themeService.getFileIconTheme()); + await picker.openQuickPick(picks, themeService.getFileIconTheme()); } -} +}); +const SelectProductIconThemeCommandId = 'workbench.action.selectProductIconTheme'; -class SelectProductIconThemeAction extends AbstractSelectThemeAction { +registerAction2(class extends Action2 { - static readonly ID = 'workbench.action.selectProductIconTheme'; - static readonly LABEL = localize('selectProductIconTheme.label', "Product Icon Theme"); + constructor() { + super({ + id: SelectProductIconThemeCommandId, + title: localize('selectProductIconTheme.label', "Product Icon Theme"), + category: CATEGORIES.Preferences, + f1: true + }); + } - constructor( - id: string, - label: string, - @IQuickInputService quickInputService: IQuickInputService, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, - @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, - @IPaneCompositePartService paneCompositeService: IPaneCompositePartService, - @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, - @IInstantiationService instantiationService: IInstantiationService + override async run(accessor: ServicesAccessor) { + const themeService = accessor.get(IWorkbenchThemeService); - ) { - super(id, label, quickInputService, extensionGalleryService, paneCompositeService, extensionResourceLoaderService, instantiationService); - } + const installMessage = localize('installProductIconThemes', "Install Additional Product Icon Themes..."); + const browseMessage = '$(plus) ' + localize('browseProductIconThemes', "Browse Additional Product Icon Themes..."); + const placeholderMessage = localize('themes.selectProductIconTheme', "Select Product Icon Theme (Up/Down Keys to Preview)"); + const marketplaceTag = 'tag:product-icon-theme'; + const setTheme = (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => themeService.setProductIconTheme(theme as IWorkbenchProductIconTheme, settingsTarget); + const getMarketplaceColorThemes = (publisher: string, name: string, version: string) => themeService.getMarketplaceProductIconThemes(publisher, name, version); + + const instantiationService = accessor.get(IInstantiationService); + const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, browseMessage, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes); - protected override readonly installMessage = localize('installProductIconThemes', "Install Additional Product Icon Themes..."); - protected override readonly browseMessage = localize('browseProductIconThemes', "$(plus) Browse Additional Product Icon Themes..."); - protected override readonly placeholderMessage = localize('themes.selectProductIconTheme', "Select Product Icon Theme"); - protected override readonly marketplaceTag = 'tag:product-icon-theme'; - protected override setTheme(theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget): Promise { - return this.themeService.setProductIconTheme(theme as IWorkbenchProductIconTheme, settingsTarget); - } - protected override getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise { - return this.themeService.getMarketplaceProductIconThemes(publisher, name, version); - } - override async run(): Promise { const picks: QuickPickInput[] = [ { type: 'separator', label: localize('productIconThemeCategory', 'product icon themes') }, { id: DEFAULT_PRODUCT_ICON_THEME_ID, label: localize('defaultProductIconThemeLabel', 'Default') }, - ...toEntries(await this.themeService.getProductIconThemes()), + ...toEntries(await themeService.getProductIconThemes()), ]; - await this.pick(picks, this.themeService.getProductIconTheme()); + await picker.openQuickPick(picks, themeService.getProductIconTheme()); } -} +}); function configurationEntries(label: string): QuickPickInput[] { return [ @@ -522,27 +508,26 @@ const configureButton: IQuickInputButton = { iconClass: ThemeIcon.asClassName(manageExtensionIcon), tooltip: localize('manage extension', "Manage Extension"), }; -class GenerateColorThemeAction extends Action { - - static readonly ID = 'workbench.action.generateColorTheme'; - static readonly LABEL = localize('generateColorTheme.label', "Generate Color Theme From Current Settings"); - constructor( - id: string, - label: string, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, - @IEditorService private readonly editorService: IEditorService, - ) { - super(id, label); +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.generateColorTheme', + title: localize('generateColorTheme.label', "Generate Color Theme From Current Settings"), + category: CATEGORIES.Developer, + f1: true + }); } - override run(): Promise { - let theme = this.themeService.getColorTheme(); - let colors = Registry.as(ColorRegistryExtensions.ColorContribution).getColors(); - let colorIds = colors.map(c => c.id).sort(); - let resultingColors: { [key: string]: string | null } = {}; - let inherited: string[] = []; - for (let colorId of colorIds) { + override run(accessor: ServicesAccessor) { + const themeService = accessor.get(IWorkbenchThemeService); + + const theme = themeService.getColorTheme(); + const colors = Registry.as(ColorRegistryExtensions.ColorContribution).getColors(); + const colorIds = colors.map(c => c.id).sort(); + const resultingColors: { [key: string]: string | null } = {}; + const inherited: string[] = []; + for (const colorId of colorIds) { const color = theme.getColor(colorId, false); if (color) { resultingColors[colorId] = Color.Format.CSS.formatHexA(color, true); @@ -551,7 +536,7 @@ class GenerateColorThemeAction extends Action { } } const nullDefaults = []; - for (let id of inherited) { + for (const id of inherited) { const color = theme.getColor(id); if (color) { resultingColors['__' + id] = Color.Format.CSS.formatHexA(color, true); @@ -559,7 +544,7 @@ class GenerateColorThemeAction extends Action { nullDefaults.push(id); } } - for (let id of nullDefaults) { + for (const id of nullDefaults) { resultingColors['__' + id] = null; } let contents = JSON.stringify({ @@ -570,29 +555,15 @@ class GenerateColorThemeAction extends Action { }, null, '\t'); contents = contents.replace(/\"__/g, '//"'); - return this.editorService.openEditor({ resource: undefined, contents, mode: 'jsonc', options: { pinned: true } }); + const editorService = accessor.get(IEditorService); + return editorService.openEditor({ resource: undefined, contents, mode: 'jsonc', options: { pinned: true } }); } -} - -const category = localize('preferences', "Preferences"); - -const colorThemeDescriptor = SyncActionDescriptor.from(SelectColorThemeAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyT) }); -Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(colorThemeDescriptor, 'Preferences: Color Theme', category); - -const fileIconThemeDescriptor = SyncActionDescriptor.from(SelectFileIconThemeAction); -Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(fileIconThemeDescriptor, 'Preferences: File Icon Theme', category); - -const productIconThemeDescriptor = SyncActionDescriptor.from(SelectProductIconThemeAction); -Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(productIconThemeDescriptor, 'Preferences: Product Icon Theme', category); - - -const generateColorThemeDescriptor = SyncActionDescriptor.from(GenerateColorThemeAction); -Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(generateColorThemeDescriptor, 'Developer: Generate Color Theme From Current Settings', CATEGORIES.Developer.value); +}); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { group: '4_themes', command: { - id: SelectColorThemeAction.ID, + id: SelectColorThemeCommandId, title: localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme") }, order: 1 @@ -601,7 +572,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { group: '4_themes', command: { - id: SelectFileIconThemeAction.ID, + id: SelectFileIconThemeCommandId, title: localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme") }, order: 2 @@ -610,7 +581,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { group: '4_themes', command: { - id: SelectProductIconThemeAction.ID, + id: SelectProductIconThemeCommandId, title: localize({ key: 'miSelectProductIconTheme', comment: ['&& denotes a mnemonic'] }, "&&Product Icon Theme") }, order: 3 @@ -620,7 +591,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '4_themes', command: { - id: SelectColorThemeAction.ID, + id: SelectColorThemeCommandId, title: localize('selectTheme.label', "Color Theme") }, order: 1 @@ -629,7 +600,7 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '4_themes', command: { - id: SelectFileIconThemeAction.ID, + id: SelectFileIconThemeCommandId, title: localize('themes.selectIconTheme.label', "File Icon Theme") }, order: 2 @@ -638,7 +609,7 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '4_themes', command: { - id: SelectProductIconThemeAction.ID, + id: SelectProductIconThemeCommandId, title: localize('themes.selectProductIconTheme.label', "Product Icon Theme") }, order: 3 From be87ebcd0dfceb7d2f1ceb128e0f16e39fc1bf13 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 22:41:43 +0100 Subject: [PATCH 0053/2210] Ask the client to pause writing until the socket is sent to the remote extension host process, which then asks the client to resume (#134429) --- src/vs/base/parts/ipc/common/ipc.net.ts | 111 +++++++++++++----- .../base/parts/ipc/test/node/ipc.net.test.ts | 53 +++++++++ .../server/remoteExtensionHostAgentServer.ts | 2 + .../node/extensionHostProcessSetup.ts | 2 + 4 files changed, 139 insertions(+), 29 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 6b72f2f2dadac..4c8ddd29b4308 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -254,7 +254,9 @@ const enum ProtocolMessageType { Control = 2, Ack = 3, Disconnect = 5, - ReplayRequest = 6 + ReplayRequest = 6, + Pause = 7, + Resume = 8 } function protocolMessageTypeToString(messageType: ProtocolMessageType) { @@ -265,6 +267,8 @@ function protocolMessageTypeToString(messageType: ProtocolMessageType) { case ProtocolMessageType.Ack: return 'Ack'; case ProtocolMessageType.Disconnect: return 'Disconnect'; case ProtocolMessageType.ReplayRequest: return 'ReplayRequest'; + case ProtocolMessageType.Pause: return 'PauseWriting'; + case ProtocolMessageType.Resume: return 'ResumeWriting'; } } @@ -432,10 +436,15 @@ class ProtocolWriter { this._writeNow(); } - public pause() { + public pause(): void { this._isPaused = true; } + public resume(): void { + this._isPaused = false; + this._scheduleWriting(); + } + public write(msg: ProtocolMessage) { if (this._isDisposed) { // ignore: there could be left-over promises which complete and then @@ -472,10 +481,19 @@ class ProtocolWriter { private _writeSoon(header: VSBuffer, data: VSBuffer): void { if (this._bufferAdd(header, data)) { - setTimeout(() => { - this._writeNow(); - }); + this._scheduleWriting(); + } + } + + private _writeNowTimeout: any = null; + private _scheduleWriting(): void { + if (this._writeNowTimeout) { + return; } + this._writeNowTimeout = setTimeout(() => { + this._writeNowTimeout = null; + this._writeNow(); + }); } private _writeNow(): void { @@ -837,6 +855,16 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketWriter.flush(); } + sendPause(): void { + const msg = new ProtocolMessage(ProtocolMessageType.Pause, 0, 0, getEmptyBuffer()); + this._socketWriter.write(msg); + } + + sendResume(): void { + const msg = new ProtocolMessage(ProtocolMessageType.Resume, 0, 0, getEmptyBuffer()); + this._socketWriter.write(msg); + } + pauseSocketWriting() { this._socketWriter.pause(); } @@ -899,34 +927,59 @@ export class PersistentProtocol implements IMessagePassingProtocol { } while (true); } - if (msg.type === ProtocolMessageType.Regular) { - if (msg.id > this._incomingMsgId) { - if (msg.id !== this._incomingMsgId + 1) { - // in case we missed some messages we ask the other party to resend them - const now = Date.now(); - if (now - this._lastReplayRequestTime > 10000) { - // send a replay request at most once every 10s - this._lastReplayRequestTime = now; - this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.ReplayRequest, 0, 0, getEmptyBuffer())); + switch (msg.type) { + case ProtocolMessageType.None: { + // N/A + break; + } + case ProtocolMessageType.Regular: { + if (msg.id > this._incomingMsgId) { + if (msg.id !== this._incomingMsgId + 1) { + // in case we missed some messages we ask the other party to resend them + const now = Date.now(); + if (now - this._lastReplayRequestTime > 10000) { + // send a replay request at most once every 10s + this._lastReplayRequestTime = now; + this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.ReplayRequest, 0, 0, getEmptyBuffer())); + } + } else { + this._incomingMsgId = msg.id; + this._incomingMsgLastTime = Date.now(); + this._sendAckCheck(); + this._onMessage.fire(msg.data); } - } else { - this._incomingMsgId = msg.id; - this._incomingMsgLastTime = Date.now(); - this._sendAckCheck(); - this._onMessage.fire(msg.data); } + break; } - } else if (msg.type === ProtocolMessageType.Control) { - this._onControlMessage.fire(msg.data); - } else if (msg.type === ProtocolMessageType.Disconnect) { - this._onDidDispose.fire(); - } else if (msg.type === ProtocolMessageType.ReplayRequest) { - // Send again all unacknowledged messages - const toSend = this._outgoingUnackMsg.toArray(); - for (let i = 0, len = toSend.length; i < len; i++) { - this._socketWriter.write(toSend[i]); + case ProtocolMessageType.Control: { + this._onControlMessage.fire(msg.data); + break; + } + case ProtocolMessageType.Ack: { + // nothing to do + break; + } + case ProtocolMessageType.Disconnect: { + this._onDidDispose.fire(); + break; + } + case ProtocolMessageType.ReplayRequest: { + // Send again all unacknowledged messages + const toSend = this._outgoingUnackMsg.toArray(); + for (let i = 0, len = toSend.length; i < len; i++) { + this._socketWriter.write(toSend[i]); + } + this._recvAckCheck(); + break; + } + case ProtocolMessageType.Pause: { + this._socketWriter.pause(); + break; + } + case ProtocolMessageType.Resume: { + this._socketWriter.resume(); + break; } - this._recvAckCheck(); } } diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index b8f3c3d2ebe8c..ec49f0069b6b1 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -364,6 +364,59 @@ suite('PersistentProtocol reconnection', () => { } ); }); + + test('writing can be paused', async () => { + await runWithFakedTimers({ useFakeTimers: true, maxTaskCount: 100 }, async () => { + const loadEstimator: ILoadEstimator = { + hasHighLoad: () => false + }; + const ether = new Ether(); + const aSocket = new NodeSocket(ether.a); + const a = new PersistentProtocol(aSocket, null, loadEstimator); + const aMessages = new MessageStream(a); + const bSocket = new NodeSocket(ether.b); + const b = new PersistentProtocol(bSocket, null, loadEstimator); + const bMessages = new MessageStream(b); + + // send one message A -> B + a.send(VSBuffer.fromString('a1')); + const a1 = await bMessages.waitForOne(); + assert.strictEqual(a1.toString(), 'a1'); + + // ask A to pause writing + b.sendPause(); + + // send a message B -> A + b.send(VSBuffer.fromString('b1')); + const b1 = await aMessages.waitForOne(); + assert.strictEqual(b1.toString(), 'b1'); + + // send a message A -> B (this should be blocked at A) + a.send(VSBuffer.fromString('a2')); + + // wait a long time and check that not even acks are written + await timeout(2 * ProtocolConstants.AcknowledgeTime); + assert.strictEqual(a.unacknowledgedCount, 1); + assert.strictEqual(b.unacknowledgedCount, 1); + + // ask A to resume writing + b.sendResume(); + + // check that B receives message + const a2 = await bMessages.waitForOne(); + assert.strictEqual(a2.toString(), 'a2'); + + // wait a long time and check that acks are written + await timeout(2 * ProtocolConstants.AcknowledgeTime); + assert.strictEqual(a.unacknowledgedCount, 0); + assert.strictEqual(b.unacknowledgedCount, 0); + + aMessages.dispose(); + bMessages.dispose(); + a.dispose(); + b.dispose(); + }); + }); }); suite('IPC, create handle', () => { diff --git a/src/vs/server/remoteExtensionHostAgentServer.ts b/src/vs/server/remoteExtensionHostAgentServer.ts index b3a66234045ba..24007c162b093 100644 --- a/src/vs/server/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/remoteExtensionHostAgentServer.ts @@ -754,6 +754,7 @@ export class RemoteExtensionHostAgentServer extends Disposable { } } + protocol.sendPause(); protocol.sendControl(VSBuffer.fromString(JSON.stringify(startParams.port ? { debugPort: startParams.port } : {}))); const dataChunk = protocol.readEntireBuffer(); protocol.dispose(); @@ -766,6 +767,7 @@ export class RemoteExtensionHostAgentServer extends Disposable { return this._rejectWebSocketConnection(logPrefix, protocol, `Duplicate reconnection token`); } + protocol.sendPause(); protocol.sendControl(VSBuffer.fromString(JSON.stringify(startParams.port ? { debugPort: startParams.port } : {}))); const dataChunk = protocol.readEntireBuffer(); protocol.dispose(); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 36dc1a6416640..e9a12654d26e7 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -134,9 +134,11 @@ function _createExtHostProtocol(): Promise { disconnectRunner2.cancel(); protocol.beginAcceptReconnection(socket, initialDataChunk); protocol.endAcceptReconnection(); + protocol.sendResume(); } else { clearTimeout(timer); protocol = new PersistentProtocol(socket, initialDataChunk); + protocol.sendResume(); protocol.onDidDispose(() => onTerminate('renderer disconnected')); resolve(protocol); From eecd0038f6c149944b401739ff22529b019124c2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 25 Nov 2021 22:51:41 +0100 Subject: [PATCH 0054/2210] add previewColorTheme command (for #137289) --- .../themes/browser/themes.contribution.ts | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 68f6570af9d54..89cf39b1173d6 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -32,6 +32,7 @@ import { Emitter } from 'vs/base/common/event'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.')); @@ -235,6 +236,7 @@ class MarketplaceThemesPicker { } } + class InstalledThemesPicker { constructor( private readonly installMessage: string, @@ -252,10 +254,10 @@ class InstalledThemesPicker { } public async openQuickPick(picks: QuickPickInput[], currentTheme: IWorkbenchTheme) { - let marketplaceThemes: MarketplaceThemesPicker | undefined; + let marketplaceThemePicker: MarketplaceThemesPicker | undefined; if (this.extensionGalleryService.isEnabled()) { if (this.extensionResourceLoaderService.supportsExtensionGalleryResources) { - marketplaceThemes = this.instantiationService.createInstance(MarketplaceThemesPicker, this.getMarketplaceColorThemes.bind(this), this.marketplaceTag); + marketplaceThemePicker = this.instantiationService.createInstance(MarketplaceThemesPicker, this.getMarketplaceColorThemes.bind(this), this.marketplaceTag); picks = [...configurationEntries(this.browseMessage), ...picks]; } else { picks = [...picks, ...configurationEntries(this.installMessage)]; @@ -264,7 +266,7 @@ class InstalledThemesPicker { let selectThemeTimeout: number | undefined; - const selectTheme = (theme: ThemeItem | undefined, applyTheme: boolean) => { + const selectTheme = (theme: IWorkbenchTheme | undefined, applyTheme: boolean) => { if (selectThemeTimeout) { clearTimeout(selectThemeTimeout); } @@ -294,8 +296,8 @@ class InstalledThemesPicker { isCompleted = true; const theme = quickpick.selectedItems[0]; if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry - if (marketplaceThemes) { - const res = await marketplaceThemes.openQuickPick(quickpick.value, currentTheme, selectTheme); + if (marketplaceThemePicker) { + const res = await marketplaceThemePicker.openQuickPick(quickpick.value, currentTheme, selectTheme); if (res === 'back') { await pickInstalledThemes(undefined); } @@ -303,13 +305,13 @@ class InstalledThemesPicker { openExtensionViewlet(this.paneCompositeService, `${this.marketplaceTag} ${quickpick.value}`); } } else { - selectTheme(theme, true); + selectTheme(theme.theme, true); } quickpick.hide(); s(); }); - quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); + quickpick.onDidChangeActive(themes => selectTheme(themes[0].theme, false)); quickpick.onDidHide(() => { if (!isCompleted) { selectTheme(currentTheme, true); @@ -332,7 +334,7 @@ class InstalledThemesPicker { }; await pickInstalledThemes(currentTheme.id); - marketplaceThemes?.dispose(); + marketplaceThemePicker?.dispose(); } } @@ -451,6 +453,19 @@ registerAction2(class extends Action2 { } }); +CommandsRegistry.registerCommand('workbench.action.previewColorTheme', async function (accessor: ServicesAccessor, extension: { publisher: string, name: string, version: string }, themeSettingsId?: string) { + const themeService = accessor.get(IWorkbenchThemeService); + + const themes = await themeService.getMarketplaceColorThemes(extension.publisher, extension.name, extension.version); + for (const theme of themes) { + if (!themeSettingsId || theme.settingsId === themeSettingsId) { + await themeService.setColorTheme(theme, 'preview'); + return theme.settingsId; + } + } + return undefined; +}); + function configurationEntries(label: string): QuickPickInput[] { return [ { From 8dbd9d0ee65440f09230916ce48f4f40559dcafc Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 25 Nov 2021 23:40:30 +0100 Subject: [PATCH 0055/2210] Remove unnecessary `socket.pause()` calls --- src/vs/server/extensionHostConnection.ts | 2 -- src/vs/workbench/contrib/remote/common/remote.contribution.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/server/extensionHostConnection.ts b/src/vs/server/extensionHostConnection.ts index 3f8b75c36490d..261aa65909759 100644 --- a/src/vs/server/extensionHostConnection.ts +++ b/src/vs/server/extensionHostConnection.ts @@ -110,7 +110,6 @@ export class ExtensionHostConnection { this._remoteAddress = remoteAddress; this._extensionHostProcess = null; this._connectionData = ExtensionHostConnection._toConnectionData(socket, initialDataChunk); - this._connectionData.socket.pause(); this._log(`New connection established.`); } @@ -156,7 +155,6 @@ export class ExtensionHostConnection { this._remoteAddress = remoteAddress; this._log(`The client has reconnected.`); const connectionData = ExtensionHostConnection._toConnectionData(_socket, initialDataChunk); - connectionData.socket.pause(); if (!this._extensionHostProcess) { // The extension host didn't even start up yet diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index ad750d87ffd2c..83c158dd41b99 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -165,7 +165,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(RemoteLogOutputChan workbenchContributionsRegistry.registerWorkbenchContribution(TunnelFactoryContribution, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(ShowCandidateContribution, LifecyclePhase.Ready); -const enableDiagnostics = false; +const enableDiagnostics = true; if (enableDiagnostics) { class TriggerReconnectAction extends Action2 { From 503a9bcd16892e2b99155457661d53fcdf6478b6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 01:06:49 +0100 Subject: [PATCH 0056/2210] #15756 prompt users to migrate from old extension to main prerelease extension --- .../common/extensionGalleryService.ts | 14 +++- .../common/extensionManagement.ts | 2 + .../browser/extensions.contribution.ts | 2 + .../extensions/browser/extensionsActions.ts | 36 +++++++++- .../browser/extensionsWorkbenchService.ts | 45 ++++++------ .../unsupportedPreReleaseExtensionsChecker.ts | 72 +++++++++++++++++++ 6 files changed, 147 insertions(+), 24 deletions(-) create mode 100644 src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 8585f92a88480..6f15f76499f68 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -5,6 +5,7 @@ import { distinct } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IStringDictionary } from 'vs/base/common/collections'; import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { getOrDefault } from 'vs/base/common/objects'; import { IPager } from 'vs/base/common/paging'; @@ -440,7 +441,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller interface IRawExtensionsControlManifest { malicious: string[]; - slow: string[]; + unsupported: IStringDictionary; } abstract class AbstractExtensionGalleryService implements IExtensionGalleryService { @@ -958,14 +959,23 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const result = await asJson(context); const malicious: IExtensionIdentifier[] = []; + const unsupportedPreReleaseExtensions: IStringDictionary<{ id: string, displayName: string }> = {}; if (result) { for (const id of result.malicious) { malicious.push({ id }); } + if (result.unsupported) { + for (const extensionId of Object.keys(result.unsupported)) { + const value = result.unsupported[extensionId]; + if (!isBoolean(value)) { + unsupportedPreReleaseExtensions[extensionId.toLowerCase()] = value.preReleaseExtension; + } + } + } } - return { malicious }; + return { malicious, unsupportedPreReleaseExtensions }; } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index fe4a211e5a66a..15afacc785621 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; import { FileAccess } from 'vs/base/common/network'; import { IPager } from 'vs/base/common/paging'; @@ -312,6 +313,7 @@ export const enum StatisticType { export interface IExtensionsControlManifest { malicious: IExtensionIdentifier[]; + unsupportedPreReleaseExtensions?: IStringDictionary<{ id: string, displayName: string }>; } export const enum InstallOperation { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index c114661a85a11..24ce51098e4ae 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -74,6 +74,7 @@ import { ExtensionsCompletionItemsProvider } from 'vs/workbench/contrib/extensio import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Event } from 'vs/base/common/event'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { UnsupportedPreReleaseExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -1461,6 +1462,7 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(UnsupportedPreReleaseExtensionsChecker, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 940748768725d..cbb2a5fedafca 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { dispose } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -1125,6 +1125,40 @@ export class SwitchToReleasedVersionAction extends ExtensionAction { } } +export class SwitchUnsupportedExtensionToPreReleaseExtensionAction extends Action { + + private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled`; + + constructor( + private readonly local: ILocalExtension, + private readonly gallery: IGalleryExtension, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IProductService private readonly productService: IProductService, + @IHostService private readonly hostService: IHostService, + @IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService, + @INotificationService private readonly notificationService: INotificationService, + ) { + super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtension', localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}' Pre-Release version", gallery.displayName), SwitchUnsupportedExtensionToPreReleaseExtensionAction.Class); + } + + override async run(): Promise { + await Promise.all([ + this.extensionManagementService.uninstall(this.local), + this.extensionManagementService.installFromGallery(this.gallery, { installPreReleaseVersion: true, isMachineScoped: this.local.isMachineScoped }) + .then(local => this.workbenchExtensionEnablementService.setEnablement([this.local], EnablementState.EnabledGlobally)), + ]); + this.notificationService.prompt( + Severity.Info, + localize('SwitchToAnotherReleaseExtension.successReload', "Please reload {0} to complete switching to the '{1}' extension.", this.productService.nameLong, this.gallery.displayName), + [{ + label: localize('reloadNow', "Reload Now"), + run: () => this.hostService.reload() + }], + { sticky: true } + ); + } +} + export class InstallAnotherVersionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.install.anotherVersion'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 9d9dff33d8e12..aa88093a29ba1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -14,10 +14,10 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, - InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, isIExtensionIdentifier + InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, isIExtensionIdentifier, IExtensionsControlManifest } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -418,28 +418,32 @@ class Extensions extends Disposable { return this.local; } - async syncLocalWithGalleryExtension(gallery: IGalleryExtension, maliciousExtensionSet: Set): Promise { + async syncLocalWithGalleryExtension(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): Promise { const extension = this.getInstalledExtensionMatchingGallery(gallery); - if (!extension) { + if (!extension?.local) { return false; } - if (maliciousExtensionSet.has(extension.identifier.id)) { - extension.isMalicious = true; + + let hasChanged: boolean = false; + + const isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier)); + if (extension.isMalicious !== isMalicious) { + extension.isMalicious = isMalicious; + hasChanged = true; } const compatible = await this.getCompatibleExtension(gallery, !!extension.local?.isPreReleaseVersion); - if (!compatible) { - return false; - } - // Sync the local extension with gallery extension if local extension doesnot has metadata - if (extension.local) { + if (compatible) { const local = extension.local.identifier.uuid ? extension.local : await this.server.extensionManagementService.updateMetadata(extension.local, { id: compatible.identifier.uuid, publisherDisplayName: compatible.publisherDisplayName, publisherId: compatible.publisherId }); extension.local = local; extension.gallery = compatible; + hasChanged = true; + } + + if (hasChanged) { this._onChange.fire({ extension }); - return true; } - return false; + return hasChanged; } private async getCompatibleExtension(extensionOrIdentifier: IGalleryExtension | IExtensionIdentifier, includePreRelease: boolean): Promise { @@ -749,11 +753,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension options.text = options.text ? this.resolveQueryText(options.text) : options.text; options.includePreRelease = isUndefined(options.includePreRelease) ? this.preferPreReleases : options.includePreRelease; - const report = await this.extensionManagementService.getExtensionsControlManifest(); - const maliciousSet = getMaliciousExtensionsSet(report); + const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest(); try { const result = await this.galleryService.query(options, token); - return mapPager(result, gallery => this.fromGallery(gallery, maliciousSet)); + return mapPager(result, gallery => this.fromGallery(gallery, extensionsControlManifest)); } catch (error) { if (/No extension gallery service configured/.test(error.message)) { return Promise.resolve(singlePagePager([])); @@ -890,11 +893,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return extension || extensions[0]; } - private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set): IExtension { + private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension { Promise.all([ - this.localExtensions ? this.localExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false), - this.remoteExtensions ? this.remoteExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false), - this.webExtensions ? this.webExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false) + this.localExtensions ? this.localExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false), + this.remoteExtensions ? this.remoteExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false), + this.webExtensions ? this.webExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false) ]) .then(result => { if (result[0] || result[1] || result[2]) { @@ -907,7 +910,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return installed; } const extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), undefined, undefined, gallery); - if (maliciousExtensionSet.has(extension.identifier.id)) { + if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) { extension.isMalicious = true; } return extension; diff --git a/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts b/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts new file mode 100644 index 0000000000000..75ee92f4b3a56 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { SwitchUnsupportedExtensionToPreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; + +export class UnsupportedPreReleaseExtensionsChecker implements IWorkbenchContribution { + + constructor( + @INotificationService private readonly notificationService: INotificationService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + this.notifyUnsupportedPreReleaseExtensions(); + } + + private async notifyUnsupportedPreReleaseExtensions(): Promise { + const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest(); + if (!extensionsControlManifest.unsupportedPreReleaseExtensions) { + return; + } + + const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + const unsupportedLocalExtensionsWithIdentifiers: [ILocalExtension, IExtensionIdentifier][] = []; + for (const extension of installed) { + const preReleaseExtension = extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()]; + if (preReleaseExtension) { + unsupportedLocalExtensionsWithIdentifiers.push([extension, { id: preReleaseExtension.id }]); + } + } + if (!unsupportedLocalExtensionsWithIdentifiers.length) { + return; + } + + const unsupportedPreReleaseExtensions: [ILocalExtension, IGalleryExtension][] = []; + const galleryExensions = await this.extensionGalleryService.getExtensions(unsupportedLocalExtensionsWithIdentifiers.map(([, identifier]) => identifier), true, CancellationToken.None); + for (const gallery of galleryExensions) { + const unsupportedLocalExtension = unsupportedLocalExtensionsWithIdentifiers.find(([, identifier]) => areSameExtensions(identifier, gallery.identifier)); + if (unsupportedLocalExtension) { + unsupportedPreReleaseExtensions.push([unsupportedLocalExtension[0], gallery]); + } + } + if (!unsupportedPreReleaseExtensions.length) { + return; + } + + if (unsupportedPreReleaseExtensions.length === 1) { + const [local, gallery] = unsupportedPreReleaseExtensions[0]; + const action = this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionAction, unsupportedPreReleaseExtensions[0][0], unsupportedPreReleaseExtensions[0][1]); + this.notificationService.notify({ + severity: Severity.Info, + message: localize('unsupported prerelease message', "'{0}' extension is now part of the '{1}' extension as a pre-release version and it is no longer supported. Would you like to switch to '{2}' extension?", local.manifest.displayName || local.identifier.id, gallery.displayName, gallery.displayName), + actions: { + primary: [action] + }, + sticky: true + }); + return; + } + } + +} From a45c7f09cc9509b6708c9c7529dd02f5122ccf6f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 02:02:41 +0100 Subject: [PATCH 0057/2210] #15756 - show warning for unsupported extensions - action to switch to prerelease --- .../extensions/browser/extensionEditor.ts | 3 +- .../extensions/browser/extensionsActions.ts | 68 +++++++++++++++---- .../browser/extensionsWorkbenchService.ts | 22 ++++++ .../unsupportedPreReleaseExtensionsChecker.ts | 4 +- .../contrib/extensions/common/extensions.ts | 1 + 5 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 1bc79eded972e..25ae36d54c6d5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -30,7 +30,7 @@ import { UpdateAction, ReloadAction, EnableDropDownAction, DisableDropDownAction, ExtensionStatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, - InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction + InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SwitchUnsupportedExtensionToPreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -456,6 +456,7 @@ export class ExtensionEditor extends EditorPane { ]), this.instantiationService.createInstance(SwitchToPreReleaseVersionAction), this.instantiationService.createInstance(SwitchToReleasedVersionAction), + this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionAction), this.instantiationService.createInstance(ToggleSyncExtensionAction), new ExtensionEditorManageExtensionAction(this.scopedContextKeyService || this.contextKeyService, this.instantiationService), ]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index cbb2a5fedafca..39e5da42b8823 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -64,6 +64,7 @@ import { escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from 'vs/ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { flatten } from 'vs/base/common/arrays'; +import { isBoolean } from 'vs/base/common/types'; function getRelativeDateLabel(date: Date): string { const delta = new Date().getTime() - date.getTime(); @@ -1125,20 +1126,46 @@ export class SwitchToReleasedVersionAction extends ExtensionAction { } } -export class SwitchUnsupportedExtensionToPreReleaseExtensionAction extends Action { +export class SwitchUnsupportedExtensionToPreReleaseExtensionAction extends ExtensionAction { private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled`; + constructor( + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtension', '', SwitchUnsupportedExtensionToPreReleaseExtensionAction.Class); + } + + update(): void { + this.enabled = false; + if (!!this.extension && !!this.extension.local && this.extension.isUnsupported && !isBoolean(this.extension.isUnsupported) && this.extension.state === ExtensionState.Installed) { + this.enabled = true; + this.label = localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}' Pre-Release version", this.extension.isUnsupported.preReleaseExtension.displayName); + } + } + + override async run(): Promise { + if (!!this.extension && !!this.extension.local && this.extension.isUnsupported && !isBoolean(this.extension.isUnsupported)) { + const gallery = (await this.galleryService.getExtensions([{ id: this.extension.isUnsupported.preReleaseExtension.id }], true, CancellationToken.None))[0]; + return this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction, this.extension.local, gallery, false).run(); + } + } +} + +export class SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction extends Action { + constructor( private readonly local: ILocalExtension, private readonly gallery: IGalleryExtension, + private promptReload: boolean, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IProductService private readonly productService: IProductService, @IHostService private readonly hostService: IHostService, @IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService, @INotificationService private readonly notificationService: INotificationService, ) { - super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtension', localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}' Pre-Release version", gallery.displayName), SwitchUnsupportedExtensionToPreReleaseExtensionAction.Class); + super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtensionCommand', localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}' Pre-Release version", gallery.displayName)); } override async run(): Promise { @@ -1147,15 +1174,17 @@ export class SwitchUnsupportedExtensionToPreReleaseExtensionAction extends Actio this.extensionManagementService.installFromGallery(this.gallery, { installPreReleaseVersion: true, isMachineScoped: this.local.isMachineScoped }) .then(local => this.workbenchExtensionEnablementService.setEnablement([this.local], EnablementState.EnabledGlobally)), ]); - this.notificationService.prompt( - Severity.Info, - localize('SwitchToAnotherReleaseExtension.successReload', "Please reload {0} to complete switching to the '{1}' extension.", this.productService.nameLong, this.gallery.displayName), - [{ - label: localize('reloadNow', "Reload Now"), - run: () => this.hostService.reload() - }], - { sticky: true } - ); + if (this.promptReload) { + this.notificationService.prompt( + Severity.Info, + localize('SwitchToAnotherReleaseExtension.successReload', "Please reload {0} to complete switching to the '{1}' extension.", this.productService.nameLong, this.gallery.displayName), + [{ + label: localize('reloadNow', "Reload Now"), + run: () => this.hostService.reload() + }], + { sticky: true } + ); + } } } @@ -2211,12 +2240,21 @@ export class ExtensionStatusAction extends ExtensionAction { return; } - if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) { - if (this.extension.isMalicious) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('malicious tooltip', "This extension was reported to be problematic.")) }, true); - return; + if (this.extension.isMalicious) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('malicious tooltip', "This extension was reported to be problematic.")) }, true); + return; + } + if (this.extension.isUnsupported) { + if (isBoolean(this.extension.isUnsupported)) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported tooltip', "This extension no longer supported.")) }, true); + } else { + const link = `[${this.extension.isUnsupported.preReleaseExtension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.isUnsupported.preReleaseExtension.id]))}`)})`; + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease tooltip', "This extension is now part of the {0} extension as a pre-release version and it is no longer supported.", link)) }, true); } + return; + } + if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) { if (this.extensionManagementServerService.localExtensionManagementServer || this.extensionManagementServerService.remoteExtensionManagementServer) { const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform()); const message = new MarkdownString(`${localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", this.extension.displayName || this.extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform))} [${localize('learn more', "Learn More")}](https://aka.ms/vscode-platform-specific-extensions)`); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index aa88093a29ba1..ef64a93bbe698 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -186,6 +186,7 @@ class Extension implements IExtension { } public isMalicious: boolean = false; + public isUnsupported: boolean | { preReleaseExtension: { id: string, displayName: string } } = false; get installCount(): number | undefined { return this.gallery ? this.gallery.installCount : undefined; @@ -440,6 +441,17 @@ class Extensions extends Disposable { hasChanged = true; } + const unsupportedPreRelease = extensionsControlManifest.unsupportedPreReleaseExtensions ? extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()] : undefined; + if (unsupportedPreRelease) { + if (isBoolean(extension.isUnsupported) || !areSameExtensions({ id: extension.isUnsupported.preReleaseExtension.id }, { id: unsupportedPreRelease.id })) { + extension.isUnsupported = { preReleaseExtension: unsupportedPreRelease }; + hasChanged = true; + } + } else if (extension.isUnsupported) { + extension.isUnsupported = false; + hasChanged = true; + } + if (hasChanged) { this._onChange.fire({ extension }); } @@ -913,6 +925,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) { extension.isMalicious = true; } + const unsupportedPreRelease = extensionsControlManifest.unsupportedPreReleaseExtensions ? extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()] : undefined; + if (unsupportedPreRelease) { + if (isBoolean(extension.isUnsupported) || !areSameExtensions({ id: extension.isUnsupported.preReleaseExtension.id }, { id: unsupportedPreRelease.id })) { + extension.isUnsupported = { preReleaseExtension: unsupportedPreRelease }; + } + } return extension; } @@ -1034,6 +1052,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return false; } + if (extension.isUnsupported) { + return false; + } + if (!extension.gallery) { return false; } diff --git a/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts b/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts index 75ee92f4b3a56..291901005be2e 100644 --- a/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts +++ b/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts @@ -11,7 +11,7 @@ import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/comm import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { SwitchUnsupportedExtensionToPreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; export class UnsupportedPreReleaseExtensionsChecker implements IWorkbenchContribution { @@ -56,7 +56,7 @@ export class UnsupportedPreReleaseExtensionsChecker implements IWorkbenchContrib if (unsupportedPreReleaseExtensions.length === 1) { const [local, gallery] = unsupportedPreReleaseExtensions[0]; - const action = this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionAction, unsupportedPreReleaseExtensions[0][0], unsupportedPreReleaseExtensions[0][1]); + const action = this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction, unsupportedPreReleaseExtensions[0][0], unsupportedPreReleaseExtensions[0][1], true); this.notificationService.notify({ severity: Severity.Info, message: localize('unsupported prerelease message', "'{0}' extension is now part of the '{1}' extension as a pre-release version and it is no longer supported. Would you like to switch to '{2}' extension?", local.manifest.displayName || local.identifier.id, gallery.displayName, gallery.displayName), diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index a5b9014067284..9b2a02eb47526 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -76,6 +76,7 @@ export interface IExtension { readonly local?: ILocalExtension; gallery?: IGalleryExtension; readonly isMalicious: boolean; + readonly isUnsupported: boolean | { preReleaseExtension: { id: string, displayName: string } }; } export const SERVICE_ID = 'extensionsWorkbenchService'; From 480888c7ceb7dcc1498e23b79142c22346951a57 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 26 Nov 2021 13:19:47 +0900 Subject: [PATCH 0058/2210] ci: unset SDKROOT macOS 11 is the default agent that has the correct headers by default to build for arm target. The hack was needed when the agent was still Catalina and the arm headers were not available by default. --- build/azure-pipelines/darwin/product-build-darwin.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 93f575abbeb8b..f624851baf16d 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -84,7 +84,6 @@ steps: set -e export npm_config_arch=$(VSCODE_ARCH) export npm_config_node_gyp=$(which node-gyp) - export SDKROOT=/Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk for i in {1..3}; do # try 3 times, for Terrapin yarn --frozen-lockfile && break From d18d093403b12a65350c58a7b0d5771cc1f42aba Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 26 Nov 2021 15:19:42 +0900 Subject: [PATCH 0059/2210] ci: remove xcode switch step Refs 480888c7ceb7dcc1498e23b79142c22346951a57 --- build/azure-pipelines/darwin/product-build-darwin.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index f624851baf16d..39e73b2a66172 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -37,12 +37,6 @@ steps: git config user.name "VSCode" displayName: Prepare tooling - - script: | - set -e - sudo xcode-select -s /Applications/Xcode_12.2.app - displayName: Switch to Xcode 12 - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64')) - - script: | set -e git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") From 01d1ea52e639615c4513689ce66576829438f748 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Nov 2021 08:56:58 +0100 Subject: [PATCH 0060/2210] tests - skip flaky in node.js env (#137853) --- src/vs/base/common/platform.ts | 6 +++++- .../contrib/testing/test/common/testResultService.test.ts | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 723d1c3ed3168..aa1e3dc4e1a59 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -11,6 +11,7 @@ let _isLinux = false; let _isLinuxSnap = false; let _isNative = false; let _isWeb = false; +let _isElectron = false; let _isIOS = false; let _locale: string | undefined = undefined; let _language: string = LANGUAGE_DEFAULT; @@ -61,7 +62,8 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== ' nodeProcess = process; } -const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer'; +const isElectronProcess = typeof nodeProcess?.versions?.electron === 'string'; +const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer'; export const isElectronSandboxed = isElectronRenderer && nodeProcess?.sandboxed; interface INavigator { @@ -89,6 +91,7 @@ else if (typeof nodeProcess === 'object') { _isMacintosh = (nodeProcess.platform === 'darwin'); _isLinux = (nodeProcess.platform === 'linux'); _isLinuxSnap = _isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION']; + _isElectron = isElectronProcess; _locale = LANGUAGE_DEFAULT; _language = LANGUAGE_DEFAULT; const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG']; @@ -140,6 +143,7 @@ export const isMacintosh = _isMacintosh; export const isLinux = _isLinux; export const isLinuxSnap = _isLinuxSnap; export const isNative = _isNative; +export const isElectron = _isElectron; export const isWeb = _isWeb; export const isIOS = _isIOS; export const platform = _platform; diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index 2022a47b7f9e3..49965171530a5 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -18,6 +18,7 @@ import { TestResultService } from 'vs/workbench/contrib/testing/common/testResul import { InMemoryResultStorage, ITestResultStorage } from 'vs/workbench/contrib/testing/common/testResultStorage'; import { Convert, getInitializedMainTestCollection, TestItemImpl, testStubs } from 'vs/workbench/contrib/testing/common/testStubs'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { isNative, isElectron } from 'vs/base/common/platform'; export const emptyOutputController = () => new LiveOutputController( new Lazy(() => [newWriteableBufferStream(), Promise.resolve()]), @@ -311,7 +312,11 @@ suite('Workbench - Test Results Service', () => { }); }); - test('resultItemParents', () => { + test('resultItemParents', function () { + if (isNative && !isElectron) { + this.skip(); // TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 + } + assert.deepStrictEqual([...resultItemParents(r, r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString())!)], [ r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString()), r.getStateById(new TestId(['ctrlId', 'id-a']).toString()), From a11814c57a9c85591444e1b4f1c30bb94a17108b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Nov 2021 09:22:19 +0100 Subject: [PATCH 0061/2210] smoke - switch away from potentially flaky `editors.selectTab` method --- test/automation/src/quickaccess.ts | 4 ++-- test/smoke/src/areas/workbench/data-migration.test.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts index 4d94b5950943d..17a7c61cd1202 100644 --- a/test/automation/src/quickaccess.ts +++ b/test/automation/src/quickaccess.ts @@ -80,8 +80,8 @@ export class QuickAccess { } } - async openFile(fileName: string): Promise { - await this.openQuickAccessAndWait(fileName, true); + async openFile(fileName: string, fileSearch = fileName): Promise { + await this.openQuickAccessAndWait(fileSearch, true); await this.code.dispatchKeybinding('enter'); await this.editors.waitForActiveTab(fileName); diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index def6cc5820b85..a14bb1da5c8ca 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -56,11 +56,11 @@ export function setup(opts: ParsedArgs, testDataPath: string) { await app.restart(); await app.workbench.editors.waitForTab(readmeMd, true); - await app.workbench.editors.selectTab(readmeMd); + await app.workbench.quickaccess.openFile(readmeMd); await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); await app.workbench.editors.waitForTab(untitled, true); - await app.workbench.editors.selectTab(untitled); + await app.workbench.quickaccess.openFile(untitled, textToTypeInUntitled); await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); await app.stop(); @@ -162,11 +162,11 @@ export function setup(opts: ParsedArgs, testDataPath: string) { await insidersApp.start(); await insidersApp.workbench.editors.waitForTab(readmeMd, true); - await insidersApp.workbench.editors.selectTab(readmeMd); + await insidersApp.workbench.quickaccess.openFile(readmeMd); await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); await insidersApp.workbench.editors.waitForTab(untitled, true); - await insidersApp.workbench.editors.selectTab(untitled); + await insidersApp.workbench.quickaccess.openFile(textToTypeInUntitled); await insidersApp.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); await insidersApp.stop(); From 1a3dce1b0cba4bf0aae3aef74506e40e9cea16ca Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 26 Nov 2021 10:13:08 +0100 Subject: [PATCH 0062/2210] update `monaco.d.ts` --- src/vs/monaco.d.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e4ef3980dfb6c..6792f8509c2f4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1807,7 +1807,7 @@ declare namespace monaco.editor { */ getLineLastNonWhitespaceColumn(lineNumber: number): number; /** - * Create a valid position, + * Create a valid position. */ validatePosition(position: IPosition): Position; /** @@ -1842,7 +1842,7 @@ declare namespace monaco.editor { */ getPositionAt(offset: number): Position; /** - * Get a range covering the entire model + * Get a range covering the entire model. */ getFullModelRange(): Range; /** @@ -5898,11 +5898,10 @@ declare namespace monaco.languages { /** * A string or snippet that should be inserted in a document when selecting * this completion. - * is used. */ insertText: string; /** - * Addition rules (as bitmask) that should be applied when inserting + * Additional rules (as bitmask) that should be applied when inserting * this completion. */ insertTextRules?: CompletionItemInsertTextRule; From bfd3bee27300799f534afd593b29606f6061ea0e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 10:25:43 +0100 Subject: [PATCH 0063/2210] #15756 prevent installing unsupported extensions in main service --- .../common/abstractExtensionManagementService.ts | 12 ++++++------ .../common/extensionManagement.ts | 1 + .../contrib/extensions/browser/extensionsActions.ts | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 117e765ce8b3a..e0dbc06291472 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -353,10 +353,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } private async checkAndGetCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean, installPreRelease: boolean): Promise<{ extension: IGalleryExtension, manifest: IExtensionManifest }> { - if (await this.isMalicious(extension)) { + const report = await this.getExtensionsControlManifest(); + if (getMaliciousExtensionsSet(report).has(extension.identifier.id)) { throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); } + if (!!report.unsupportedPreReleaseExtensions && !!report.unsupportedPreReleaseExtensions[extension.identifier.id]) { + throw new ExtensionManagementError(nls.localize('unsupported prerelease extension', "Can't install '{0}' extension because it is no longer supported. It is now part of the '{1}' extension as a pre-release version.", extension.identifier.id, report.unsupportedPreReleaseExtensions[extension.identifier.id].displayName), ExtensionManagementErrorCode.UnsupportedPreRelease); + } + if (!await this.canInstall(extension)) { const targetPlatform = await this.getTargetPlatform(); throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform); @@ -402,11 +407,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return compatibleExtension; } - private async isMalicious(extension: IGalleryExtension): Promise { - const report = await this.getExtensionsControlManifest(); - return getMaliciousExtensionsSet(report).has(extension.identifier.id); - } - private async unininstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise { const uninstallExtensionTask = this.uninstallingExtensions.get(extension.identifier.id.toLowerCase()); if (uninstallExtensionTask) { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 15afacc785621..5baf92d1c4a70 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -365,6 +365,7 @@ export interface DidUninstallExtensionEvent { export enum ExtensionManagementErrorCode { Unsupported = 'Unsupported', + UnsupportedPreRelease = 'UnsupportedPreRelease', Malicious = 'Malicious', Incompatible = 'Incompatible', IncompatiblePreRelease = 'IncompatiblePreRelease', diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 39e5da42b8823..14a04b375b6c1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -137,7 +137,7 @@ export class PromptExtensionInstallFailureAction extends Action { return; } - if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious].includes(this.error.name)) { + if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.UnsupportedPreRelease].includes(this.error.name)) { await this.dialogService.show(Severity.Info, getErrorMessage(this.error)); return; } From 420c749ca8454eaceeefcd35e38613299c67bc3b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Nov 2021 10:28:44 +0100 Subject: [PATCH 0064/2210] Rename resolveShellEnv to getResolvedShellEnv (fix #137923) --- src/vs/code/electron-main/app.ts | 4 ++-- src/vs/platform/environment/node/shellEnv.ts | 7 +++---- src/vs/platform/request/node/requestService.ts | 4 ++-- src/vs/platform/terminal/node/ptyHostService.ts | 4 ++-- src/vs/server/extensionHostConnection.ts | 4 ++-- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4baf8158a5928..19c575b0f27e8 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -41,7 +41,7 @@ import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encry import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; -import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv'; +import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv'; import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; @@ -1033,7 +1033,7 @@ export class CodeApplication extends Disposable { private async resolveShellEnvironment(args: NativeParsedArgs, env: IProcessEnvironment, notifyOnError: boolean): Promise { try { - return await resolveShellEnv(this.logService, args, env); + return await getResolvedShellEnv(this.logService, args, env); } catch (error) { const errorMessage = toErrorMessage(error); if (notifyOnError) { diff --git a/src/vs/platform/environment/node/shellEnv.ts b/src/vs/platform/environment/node/shellEnv.ts index a80d1f698632f..28e755f2252bc 100644 --- a/src/vs/platform/environment/node/shellEnv.ts +++ b/src/vs/platform/environment/node/shellEnv.ts @@ -27,15 +27,14 @@ const MAX_SHELL_RESOLVE_TIME = 10000; let unixShellEnvPromise: Promise | undefined = undefined; /** - * We need to get the environment from a user's shell. - * This should only be done when Code itself is not launched - * from within a shell. + * Resolves the shell environment by spawning a shell. This call will cache + * the shell spawning so that subsequent invocations use that cached result. * * Will throw an error if: * - we hit a timeout of `MAX_SHELL_RESOLVE_TIME` * - any other error from spawning a shell to figure out the environment */ -export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: IProcessEnvironment): Promise { +export async function getResolvedShellEnv(logService: ILogService, args: NativeParsedArgs, env: IProcessEnvironment): Promise { // Skip if --force-disable-user-env if (args['force-disable-user-env']) { diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 966857d8b634b..3be2146c33438 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -16,7 +16,7 @@ import { isBoolean, isNumber } from 'vs/base/common/types'; import { IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv'; +import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv'; import { ILogService } from 'vs/platform/log/common/log'; import { IHTTPConfiguration, IRequestService } from 'vs/platform/request/common/request'; import { Agent, getProxyAgent } from 'vs/platform/request/node/proxy'; @@ -67,7 +67,7 @@ export class RequestService extends Disposable implements IRequestService { let shellEnv: typeof process.env | undefined = undefined; try { - shellEnv = await resolveShellEnv(this.logService, this.environmentService.args, process.env); + shellEnv = await getResolvedShellEnv(this.logService, this.environmentService.args, process.env); } catch (error) { this.logService.error('RequestService#request resolving shell environment failed', error); } diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 5719fa039c4c4..288462f847c5f 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -12,7 +12,7 @@ import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { parsePtyHostPort } from 'vs/platform/environment/common/environmentService'; -import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv'; +import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv'; import { ILogService } from 'vs/platform/log/common/log'; import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -124,7 +124,7 @@ export class PtyHostService extends Disposable implements IPtyService { } try { - return await resolveShellEnv(this._logService, { _: [] }, process.env); + return await getResolvedShellEnv(this._logService, { _: [] }, process.env); } catch (error) { this._logService.error('ptyHost was unable to resolve shell environment', error); diff --git a/src/vs/server/extensionHostConnection.ts b/src/vs/server/extensionHostConnection.ts index 261aa65909759..537fb66b7fb75 100644 --- a/src/vs/server/extensionHostConnection.ts +++ b/src/vs/server/extensionHostConnection.ts @@ -13,7 +13,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { IRemoteConsoleLog } from 'vs/base/common/console'; import { Emitter, Event } from 'vs/base/common/event'; import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; -import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv'; +import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv'; import { ILogService } from 'vs/platform/log/common/log'; import { IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection'; import { IExtHostReadyMessage, IExtHostSocketMessage, IExtHostReduceGraceTimeMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; @@ -27,7 +27,7 @@ export async function buildUserEnvironment(startParamsEnv: { [key: string]: stri let userShellEnv: typeof process.env | undefined = undefined; try { - userShellEnv = await resolveShellEnv(logService, environmentService.args, process.env); + userShellEnv = await getResolvedShellEnv(logService, environmentService.args, process.env); } catch (error) { logService.error('ExtensionHostConnection#buildUserEnvironment resolving shell environment failed', error); userShellEnv = {}; From bab15fcbb04dfe67f047a2f9cd06c6f64073af12 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 10:44:29 +0100 Subject: [PATCH 0065/2210] #15756 fine tuning wordings --- .../contrib/extensions/browser/extensionsActions.ts | 6 +++--- .../browser/unsupportedPreReleaseExtensionsChecker.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 14a04b375b6c1..713aa0fc365d9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1141,7 +1141,7 @@ export class SwitchUnsupportedExtensionToPreReleaseExtensionAction extends Exten this.enabled = false; if (!!this.extension && !!this.extension.local && this.extension.isUnsupported && !isBoolean(this.extension.isUnsupported) && this.extension.state === ExtensionState.Installed) { this.enabled = true; - this.label = localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}' Pre-Release version", this.extension.isUnsupported.preReleaseExtension.displayName); + this.label = localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}'", this.extension.isUnsupported.preReleaseExtension.displayName); } } @@ -1165,7 +1165,7 @@ export class SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction extend @IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService, @INotificationService private readonly notificationService: INotificationService, ) { - super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtensionCommand', localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}' Pre-Release version", gallery.displayName)); + super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtensionCommand', localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}'", gallery.displayName)); } override async run(): Promise { @@ -2249,7 +2249,7 @@ export class ExtensionStatusAction extends ExtensionAction { this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported tooltip', "This extension no longer supported.")) }, true); } else { const link = `[${this.extension.isUnsupported.preReleaseExtension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.isUnsupported.preReleaseExtension.id]))}`)})`; - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease tooltip', "This extension is now part of the {0} extension as a pre-release version and it is no longer supported.", link)) }, true); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease tooltip', "This extension is no longer supported and is now part of the {0} extension as a pre-release version. We recommend that you switch to it.", link)) }, true); } return; } diff --git a/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts b/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts index 291901005be2e..0acc68df514c9 100644 --- a/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts +++ b/src/vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker.ts @@ -59,7 +59,7 @@ export class UnsupportedPreReleaseExtensionsChecker implements IWorkbenchContrib const action = this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionCommandAction, unsupportedPreReleaseExtensions[0][0], unsupportedPreReleaseExtensions[0][1], true); this.notificationService.notify({ severity: Severity.Info, - message: localize('unsupported prerelease message', "'{0}' extension is now part of the '{1}' extension as a pre-release version and it is no longer supported. Would you like to switch to '{2}' extension?", local.manifest.displayName || local.identifier.id, gallery.displayName, gallery.displayName), + message: localize('unsupported prerelease message', "'{0}' extension is no longer supported and is now part of the '{1}' extension as a pre-release version. Would you like to switch to it?", local.manifest.displayName || local.identifier.id, gallery.displayName, gallery.displayName), actions: { primary: [action] }, From 8d250e99e75850769b4e16a700cfa55efcbbcf4f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Nov 2021 11:00:11 +0100 Subject: [PATCH 0066/2210] smoke - wait for tab becoming dirty before exit --- test/smoke/src/areas/workbench/data-migration.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index a14bb1da5c8ca..45d851727961b 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -47,11 +47,13 @@ export function setup(opts: ParsedArgs, testDataPath: string) { const untitled = 'Untitled-1'; const textToTypeInUntitled = 'Hello from Untitled'; await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); + await app.workbench.editors.waitForTab(untitled, true); const readmeMd = 'readme.md'; const textToType = 'Hello, Code'; await app.workbench.quickaccess.openFile(readmeMd); await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType); + await app.workbench.editors.waitForTab(readmeMd, true); await app.restart(); @@ -146,11 +148,13 @@ export function setup(opts: ParsedArgs, testDataPath: string) { const untitled = 'Untitled-1'; const textToTypeInUntitled = 'Hello from Untitled'; await stableApp.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); + await stableApp.workbench.editors.waitForTab(untitled, true); const readmeMd = 'readme.md'; const textToType = 'Hello, Code'; await stableApp.workbench.quickaccess.openFile(readmeMd); await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType); + await stableApp.workbench.editors.waitForTab(readmeMd, true); await stableApp.stop(); stableApp = undefined; From 42ec6e7924287344cf7f55ebb69227a5dfd43d4e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 25 Nov 2021 17:53:22 +0100 Subject: [PATCH 0067/2210] Shows a banner if a file has too many highlighted unicode characters. --- src/vs/editor/browser/config/configuration.ts | 8 +- src/vs/editor/browser/editorBrowser.ts | 2 + .../editor/browser/widget/codeEditorWidget.ts | 18 ++ .../common/config/commonEditorConfig.ts | 1 + src/vs/editor/common/editorCommon.ts | 1 + .../modes/unicodeTextModelHighlighter.ts | 45 +++- .../common/services/editorSimpleWorker.ts | 6 +- .../common/services/editorWorkerService.ts | 10 +- .../services/editorWorkerServiceImpl.ts | 6 +- .../unicodeHighlighter/bannerController.css | 85 +++++++ .../unicodeHighlighter/bannerController.ts | 155 +++++++++++++ .../unicodeHighlighter/unicodeHighlighter.ts | 210 +++++++++++++++--- .../test/common/mocks/testConfiguration.ts | 4 + src/vs/monaco.d.ts | 1 + .../test/browser/workbenchTestServices.ts | 4 +- 15 files changed, 510 insertions(+), 46 deletions(-) create mode 100644 src/vs/editor/contrib/unicodeHighlighter/bannerController.css create mode 100644 src/vs/editor/contrib/unicodeHighlighter/bannerController.ts diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index f4175c0bf63a5..560e591a87874 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -314,6 +314,7 @@ export class Configuration extends CommonEditorConfiguration { } private readonly _elementSizeObserver: ElementSizeObserver; + private _reservedHeight: number = 0; constructor( isSimpleWidget: boolean, @@ -365,7 +366,7 @@ export class Configuration extends CommonEditorConfiguration { return { extraEditorClassName: Configuration._getExtraEditorClassName(), outerWidth: this._elementSizeObserver.getWidth(), - outerHeight: this._elementSizeObserver.getHeight(), + outerHeight: this._elementSizeObserver.getHeight() - this._reservedHeight, emptySelectionClipboard: browser.isWebKit || browser.isFirefox, pixelRatio: browser.getPixelRatio(), zoomLevel: browser.getZoomLevel(), @@ -380,4 +381,9 @@ export class Configuration extends CommonEditorConfiguration { protected readConfiguration(bareFontInfo: BareFontInfo): FontInfo { return CSSBasedConfiguration.INSTANCE.readConfiguration(bareFontInfo); } + + public reserveHeight(height: number) { + this._reservedHeight = height; + this._recomputeOptions(); + } } diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 5ae4919d19232..6243c0b9dd6b6 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -899,6 +899,8 @@ export interface ICodeEditor extends editorCommon.IEditor { * @internal */ hasModel(): this is IActiveCodeEditor; + + setBanner(bannerDomNode: HTMLElement | null, height: number): void; } /** diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index acb8fb98c0c29..19b22fd66a629 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -244,6 +244,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private _decorationTypeKeysToIds: { [decorationTypeKey: string]: string[] }; private _decorationTypeSubtypes: { [decorationTypeKey: string]: { [subtype: string]: boolean } }; + private _bannerDomNode: HTMLElement | null = null; + constructor( domElement: HTMLElement, _options: Readonly, @@ -1490,6 +1492,19 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE Configuration.applyFontInfoSlow(target, this._configuration.options.get(EditorOption.fontInfo)); } + public setBanner(domNode: HTMLElement | null, height: number): void { + if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) { + this._domElement.removeChild(this._bannerDomNode); + } + + this._bannerDomNode = domNode; + this._configuration.reserveHeight(height); + + if (this._bannerDomNode) { + this._domElement.prepend(this._bannerDomNode); + } + } + protected _attachModel(model: ITextModel | null): void { if (!model) { this._modelData = null; @@ -1703,6 +1718,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (removeDomNode && this._domElement.contains(removeDomNode)) { this._domElement.removeChild(removeDomNode); } + if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) { + this._domElement.removeChild(this._bannerDomNode); + } return model; } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 507fb4c8b8848..b874b8a70f8cc 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -458,6 +458,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC protected abstract readConfiguration(styling: BareFontInfo): FontInfo; + public abstract reserveHeight(height: number): void; } export const editorConfigurationBaseNode = Object.freeze({ diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 6e693abdf1a86..15e7550b29d55 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -162,6 +162,7 @@ export interface IConfiguration extends IDisposable { observeReferenceElement(dimension?: IDimension): void; updatePixelRatio(): void; setIsDominatedByLongLines(isDominatedByLongLines: boolean): void; + reserveHeight(height: number): void; } // --- view diff --git a/src/vs/editor/common/modes/unicodeTextModelHighlighter.ts b/src/vs/editor/common/modes/unicodeTextModelHighlighter.ts index 209542ea39f1c..8ca0649553e23 100644 --- a/src/vs/editor/common/modes/unicodeTextModelHighlighter.ts +++ b/src/vs/editor/common/modes/unicodeTextModelHighlighter.ts @@ -6,9 +6,11 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { Searcher } from 'vs/editor/common/model/textModelSearch'; import * as strings from 'vs/base/common/strings'; +import { IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService'; +import { assertNever } from 'vs/base/common/types'; export class UnicodeTextModelHighlighter { - public static computeUnicodeHighlights(model: IUnicodeCharacterSearcherTarget, options: UnicodeHighlighterOptions, range?: IRange): Range[] { + public static computeUnicodeHighlights(model: IUnicodeCharacterSearcherTarget, options: UnicodeHighlighterOptions, range?: IRange): IUnicodeHighlightsResult { const startLine = range ? range.startLineNumber : 1; const endLine = range ? range.endLineNumber : model.getLineCount(); @@ -23,8 +25,15 @@ export class UnicodeTextModelHighlighter { } const searcher = new Searcher(null, regex); - const result: Range[] = []; + const ranges: Range[] = []; + let hasMore = false; let m: RegExpExecArray | null; + + let ambiguousCharacterCount = 0; + let invisibleCharacterCount = 0; + let nonBasicAsciiCharacterCount = 0; + + forLoop: for (let lineNumber = startLine, lineCount = endLine; lineNumber <= lineCount; lineNumber++) { const lineContent = model.getLineContent(lineNumber); const lineLength = lineContent.length; @@ -51,19 +60,37 @@ export class UnicodeTextModelHighlighter { } } const str = lineContent.substring(startIndex, endIndex); - if (codePointHighlighter.shouldHighlightNonBasicASCII(str) !== SimpleHighlightReason.None) { - result.push(new Range(lineNumber, startIndex + 1, lineNumber, endIndex + 1)); + const highlightReason = codePointHighlighter.shouldHighlightNonBasicASCII(str); + + if (highlightReason !== SimpleHighlightReason.None) { + if (highlightReason === SimpleHighlightReason.Ambiguous) { + ambiguousCharacterCount++; + } else if (highlightReason === SimpleHighlightReason.Invisible) { + invisibleCharacterCount++; + } else if (highlightReason === SimpleHighlightReason.NonBasicASCII) { + nonBasicAsciiCharacterCount++; + } else { + assertNever(highlightReason); + } - const maxResultLength = 1000; - if (result.length > maxResultLength) { - // TODO@hediet a message should be shown in this case - break; + const MAX_RESULT_LENGTH = 1000; + if (ranges.length >= MAX_RESULT_LENGTH) { + hasMore = true; + break forLoop; } + + ranges.push(new Range(lineNumber, startIndex + 1, lineNumber, endIndex + 1)); } } } while (m); } - return result; + return { + ranges, + hasMore, + ambiguousCharacterCount, + invisibleCharacterCount, + nonBasicAsciiCharacterCount + }; } public static computeUnicodeHighlightReason(char: string, options: UnicodeHighlighterOptions): UnicodeHighlighterReason | null { diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index b87215dfe269a..8f91c6b9df47b 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -18,7 +18,7 @@ import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes'; import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/modes/linkComputer'; import { BasicInplaceReplace } from 'vs/editor/common/modes/supports/inplaceReplaceSupport'; -import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService'; +import { IDiffComputationResult, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService'; import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase'; import * as types from 'vs/base/common/types'; import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerServiceImpl'; @@ -372,10 +372,10 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { delete this._models[strURL]; } - public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise { + public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise { const model = this._getModel(url); if (!model) { - return []; + return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; } return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range); } diff --git a/src/vs/editor/common/services/editorWorkerService.ts b/src/vs/editor/common/services/editorWorkerService.ts index 753bfabc8db50..199dcab3a7a5c 100644 --- a/src/vs/editor/common/services/editorWorkerService.ts +++ b/src/vs/editor/common/services/editorWorkerService.ts @@ -23,7 +23,7 @@ export interface IEditorWorkerService { readonly _serviceBrand: undefined; canComputeUnicodeHighlights(uri: URI): boolean; - computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise; + computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise; computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise; @@ -38,3 +38,11 @@ export interface IEditorWorkerService { canNavigateValueSet(resource: URI): boolean; navigateValueSet(resource: URI, range: IRange, up: boolean): Promise; } + +export interface IUnicodeHighlightsResult { + ranges: IRange[]; + hasMore: boolean; + nonBasicAsciiCharacterCount: number; + invisibleCharacterCount: number; + ambiguousCharacterCount: number; +} diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index fd310432f73a4..f5210b5440c64 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -15,7 +15,7 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { regExpFlags } from 'vs/base/common/strings'; @@ -86,7 +86,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker return canSyncModel(this._modelService, uri); } - public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { + public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range)); } @@ -475,7 +475,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien }); } - public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { + public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { return this._withSyncedResources([uri]).then(proxy => { return proxy.computeUnicodeHighlights(uri.toString(), options, range); }); diff --git a/src/vs/editor/contrib/unicodeHighlighter/bannerController.css b/src/vs/editor/contrib/unicodeHighlighter/bannerController.css new file mode 100644 index 0000000000000..c43944d464a97 --- /dev/null +++ b/src/vs/editor/contrib/unicodeHighlighter/bannerController.css @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.editor-banner { + box-sizing: border-box; + cursor: default; + width: 100%; + font-size: 12px; + display: flex; + overflow: visible; + + height: 26px; + + background: var(--vscode-banner-background); +} + + +.editor-banner .icon-container { + display: flex; + flex-shrink: 0; + align-items: center; + padding: 0 6px 0 10px; +} + +.editor-banner .icon-container.custom-icon { + background-repeat: no-repeat; + background-position: center center; + background-size: 16px; + width: 16px; + padding: 0; + margin: 0 6px 0 10px; +} + +.editor-banner .message-container { + display: flex; + align-items: center; + line-height: 26px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.editor-banner .message-container p { + margin-block-start: 0; + margin-block-end: 0; +} + +.editor-banner .message-actions-container { + flex-grow: 1; + flex-shrink: 0; + line-height: 26px; + margin: 0 4px; +} + +.editor-banner .message-actions-container a.monaco-button { + width: inherit; + margin: 2px 8px; + padding: 0px 12px; +} + +.editor-banner .message-actions-container a { + padding: 3px; + margin-left: 12px; + text-decoration: underline; +} + +.editor-banner .action-container { + padding: 0 10px 0 6px; +} + +.editor-banner { + background-color: var(--vscode-banner-background); +} + +.editor-banner, +.editor-banner .action-container .codicon, +.editor-banner .message-actions-container .monaco-link { + color: var(--vscode-banner-foreground); +} + +.editor-banner .icon-container .codicon { + color: var(--vscode-banner-iconForeground); +} diff --git a/src/vs/editor/contrib/unicodeHighlighter/bannerController.ts b/src/vs/editor/contrib/unicodeHighlighter/bannerController.ts new file mode 100644 index 0000000000000..4276d56c1ff14 --- /dev/null +++ b/src/vs/editor/contrib/unicodeHighlighter/bannerController.ts @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./bannerController'; +import { $, append, clearNode } from 'vs/base/browser/dom'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action } from 'vs/base/common/actions'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILinkDescriptor, Link } from 'vs/platform/opener/browser/link'; +import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; + +const BANNER_ELEMENT_HEIGHT = 26; + +export class BannerController extends Disposable { + private readonly banner: Banner; + + constructor( + private readonly _editor: ICodeEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + + this.banner = this._register(this.instantiationService.createInstance(Banner)); + } + + public hide() { + this._editor.setBanner(null, 0); + this.banner.clear(); + } + + public show(item: IBannerItem) { + this.banner.show({ + ...item, + onClose: () => { + this.hide(); + if (item.onClose) { + item.onClose(); + } + } + }); + this._editor.setBanner(this.banner.element, BANNER_ELEMENT_HEIGHT); + } +} + +// TODO@hediet: Investigate if this can be reused by the workspace banner (bannerPart.ts). +class Banner extends Disposable { + public element: HTMLElement; + + private readonly markdownRenderer: MarkdownRenderer; + + private messageActionsContainer: HTMLElement | undefined; + + private actionBar: ActionBar | undefined; + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + + this.element = $('div.editor-banner'); + this.element.tabIndex = 0; + } + + private getAriaLabel(item: IBannerItem): string | undefined { + if (item.ariaLabel) { + return item.ariaLabel; + } + if (typeof item.message === 'string') { + return item.message; + } + + return undefined; + } + + private getBannerMessage(message: MarkdownString | string): HTMLElement { + if (typeof message === 'string') { + const element = $('span'); + element.innerText = message; + return element; + } + + return this.markdownRenderer.render(message).element; + } + + public clear() { + clearNode(this.element); + } + + public show(item: IBannerItem) { + // Clear previous item + clearNode(this.element); + + // Banner aria label + const ariaLabel = this.getAriaLabel(item); + if (ariaLabel) { + this.element.setAttribute('aria-label', ariaLabel); + } + + // Icon + const iconContainer = append(this.element, $('div.icon-container')); + iconContainer.setAttribute('aria-hidden', 'true'); + + if (item.icon) { + iconContainer.appendChild($(`div${ThemeIcon.asCSSSelector(item.icon)}`)); + } + + // Message + const messageContainer = append(this.element, $('div.message-container')); + messageContainer.setAttribute('aria-hidden', 'true'); + messageContainer.appendChild(this.getBannerMessage(item.message)); + + // Message Actions + this.messageActionsContainer = append(this.element, $('div.message-actions-container')); + if (item.actions) { + for (const action of item.actions) { + this._register(this.instantiationService.createInstance(Link, this.messageActionsContainer, { ...action, tabIndex: -1 }, {})); + } + } + + // Action + const actionBarContainer = append(this.element, $('div.action-container')); + this.actionBar = this._register(new ActionBar(actionBarContainer)); + this.actionBar.push(this._register( + new Action( + 'banner.close', + 'Close Banner', + ThemeIcon.asClassName(widgetClose), + true, + () => { + if (typeof item.onClose === 'function') { + item.onClose(); + } + } + ) + ), { icon: true, label: false }); + this.actionBar.setFocusable(false); + } +} + +export interface IBannerItem { + readonly id: string; + readonly icon: ThemeIcon | undefined; + readonly message: string | MarkdownString; + readonly actions?: ILinkDescriptor[]; + readonly ariaLabel?: string; + readonly onClose?: () => void; +} diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index 8550c07fef2dc..5650ff6e2fb93 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -5,6 +5,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CharCode } from 'vs/base/common/charCode'; +import { Codicon } from 'vs/base/common/codicons'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { InvisibleCharacters } from 'vs/base/common/strings'; @@ -17,32 +18,44 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecoration, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { UnicodeHighlighterOptions, UnicodeHighlighterReason, UnicodeHighlighterReasonKind, UnicodeTextModelHighlighter } from 'vs/editor/common/modes/unicodeTextModelHighlighter'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import { MarkdownHover, renderMarkdownHovers } from 'vs/editor/contrib/hover/markdownHoverParticipant'; +import { BannerController } from 'vs/editor/contrib/unicodeHighlighter/bannerController'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { minimapFindMatch, minimapUnicodeHighlight, overviewRulerFindMatchForeground, overviewRulerUnicodeHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, nls.localize('warningIcon', 'Icon shown with a warning message in the extensions editor.')); + export class UnicodeHighlighter extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.unicodeHighlighter'; private _highlighter: DocumentUnicodeHighlighter | ViewportUnicodeHighlighter | null = null; private _options: InternalUnicodeHighlightOptions; + private readonly _bannerController: BannerController; + private _bannerClosed: boolean = false; + constructor( private readonly _editor: ICodeEditor, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @IWorkspaceTrustManagementService private readonly _workspaceTrustService: IWorkspaceTrustManagementService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); + this._bannerController = this._register(instantiationService.createInstance(BannerController, _editor)); + this._register(this._editor.onDidChangeModel(() => { + this._bannerClosed = false; this._updateHighlighter(); })); @@ -70,7 +83,57 @@ export class UnicodeHighlighter extends Disposable implements IEditorContributio super.dispose(); } + private readonly _updateState = (state: IUnicodeHighlightsResult | null): void => { + if (state && state.hasMore) { + if (this._bannerClosed) { + return; + } + + // This document contains many non-basic ASCII characters. + const max = Math.max(state.ambiguousCharacterCount, state.nonBasicAsciiCharacterCount, state.invisibleCharacterCount); + + let data; + if (state.nonBasicAsciiCharacterCount >= max) { + data = { + message: nls.localize('unicodeHighlighting.thisDocumentHasManyNonBasicAsciiUnicodeCharacters', 'This document contains many non-basic ASCII unicode characters'), + command: new DisableHighlightingOfNonBasicAsciiCharactersAction(), + }; + } else if (state.ambiguousCharacterCount >= max) { + data = { + message: nls.localize('unicodeHighlighting.thisDocumentHasManyAmbiguousUnicodeCharacters', 'This document contains many ambiguous unicode characters'), + command: new DisableHighlightingOfAmbiguousCharactersAction(), + }; + } else if (state.invisibleCharacterCount >= max) { + data = { + message: nls.localize('unicodeHighlighting.thisDocumentHasManyInvisibleUnicodeCharacters', 'This document contains many invisible unicode characters'), + command: new DisableHighlightingOfInvisibleCharactersAction(), + }; + } else { + throw new Error('Unreachable'); + } + + this._bannerController.show({ + id: 'unicodeHighlightBanner', + message: data.message, + icon: warningIcon, + actions: [ + { + label: data.command.shortLabel, + href: `command:${data.command.id}` + } + ], + onClose: () => { + this._bannerClosed = true; + }, + }); + } else { + this._bannerController.hide(); + } + }; + private _updateHighlighter(): void { + this._updateState(null); + if (this._highlighter) { this._highlighter.dispose(); this._highlighter = null; @@ -100,9 +163,9 @@ export class UnicodeHighlighter extends Disposable implements IEditorContributio }; if (this._editorWorkerService.canComputeUnicodeHighlights(this._editor.getModel().uri)) { - this._highlighter = new DocumentUnicodeHighlighter(this._editor, highlightOptions, this._editorWorkerService); + this._highlighter = new DocumentUnicodeHighlighter(this._editor, highlightOptions, this._updateState, this._editorWorkerService); } else { - this._highlighter = new ViewportUnicodeHighlighter(this._editor, highlightOptions); + this._highlighter = new ViewportUnicodeHighlighter(this._editor, highlightOptions, this._updateState); } } @@ -156,6 +219,7 @@ class DocumentUnicodeHighlighter extends Disposable { constructor( private readonly _editor: IActiveCodeEditor, private readonly _options: UnicodeHighlighterOptions, + private readonly _updateState: (state: IUnicodeHighlightsResult | null) => void, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, ) { super(); @@ -182,14 +246,20 @@ class DocumentUnicodeHighlighter extends Disposable { const modelVersionId = this._model.getVersionId(); this._editorWorkerService .computedUnicodeHighlights(this._model.uri, this._options) - .then((ranges) => { + .then((info) => { if (this._model.getVersionId() !== modelVersionId) { // model changed in the meantime return; } + this._updateState(info); + const decorations: IModelDeltaDecoration[] = []; - for (const range of ranges) { - decorations.push({ range: range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS }); + if (!info.hasMore) { + // Don't show decoration if there are too many. + // In this case, a banner is shown. + for (const range of info.ranges) { + decorations.push({ range: range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS }); + } } this._decorationIds = new Set(this._editor.deltaDecorations( Array.from(this._decorationIds), @@ -218,7 +288,8 @@ class ViewportUnicodeHighlighter extends Disposable { constructor( private readonly _editor: IActiveCodeEditor, - private readonly _options: UnicodeHighlighterOptions + private readonly _options: UnicodeHighlighterOptions, + private readonly _updateState: (state: IUnicodeHighlightsResult | null) => void, ) { super(); @@ -253,12 +324,33 @@ class ViewportUnicodeHighlighter extends Disposable { const ranges = this._editor.getVisibleRanges(); const decorations: IModelDeltaDecoration[] = []; + const totalResult: IUnicodeHighlightsResult = { + ranges: [], + ambiguousCharacterCount: 0, + invisibleCharacterCount: 0, + nonBasicAsciiCharacterCount: 0, + hasMore: false, + }; for (const range of ranges) { - const ranges = UnicodeTextModelHighlighter.computeUnicodeHighlights(this._model, this._options, range); - for (const range of ranges) { + const result = UnicodeTextModelHighlighter.computeUnicodeHighlights(this._model, this._options, range); + for (const r of result.ranges) { + totalResult.ranges.push(r); + } + totalResult.ambiguousCharacterCount += totalResult.ambiguousCharacterCount; + totalResult.invisibleCharacterCount += totalResult.invisibleCharacterCount; + totalResult.nonBasicAsciiCharacterCount += totalResult.nonBasicAsciiCharacterCount; + totalResult.hasMore = totalResult.hasMore || result.hasMore; + } + + if (!totalResult.hasMore) { + // Don't show decorations if there are too many. + // A banner will be shown instead. + for (const range of totalResult.ranges) { decorations.push({ range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS }); } } + this._updateState(totalResult); + this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), decorations)); } @@ -424,6 +516,78 @@ const DECORATION = ModelDecorationOptions.register({ } }); +export class DisableHighlightingOfAmbiguousCharactersAction extends EditorAction { + public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters'; + public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters.shortLabel', ''); + constructor() { + super({ + id: DisableHighlightingOfAmbiguousCharactersAction.ID, + label: nls.localize('action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable Ambiguous Highlight'), + alias: 'Disable highlighting of ambiguous characters', + precondition: undefined + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise { + let configurationService = accessor?.get(IConfigurationService); + if (configurationService) { + this.runAction(configurationService); + } + } + + public async runAction(configurationService: IConfigurationService): Promise { + await configurationService.updateValue(unicodeHighlightConfigKeys.ambiguousCharacters, false, ConfigurationTarget.USER); + } +} + +export class DisableHighlightingOfInvisibleCharactersAction extends EditorAction { + public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfInvisibleCharacters'; + public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfInvisibleCharacters.shortLabel', 'Disable Invisible Highlight'); + constructor() { + super({ + id: DisableHighlightingOfInvisibleCharactersAction.ID, + label: nls.localize('action.unicodeHighlight.disableHighlightingOfInvisibleCharacters', 'Disable highlighting of invisible characters'), + alias: 'Disable highlighting of invisible characters', + precondition: undefined + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise { + let configurationService = accessor?.get(IConfigurationService); + if (configurationService) { + this.runAction(configurationService); + } + } + + public async runAction(configurationService: IConfigurationService): Promise { + await configurationService.updateValue(unicodeHighlightConfigKeys.invisibleCharacters, false, ConfigurationTarget.USER); + } +} + +export class DisableHighlightingOfNonBasicAsciiCharactersAction extends EditorAction { + public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters'; + public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters.shortLabel', 'Disable Non ASCII Highlight'); + constructor() { + super({ + id: DisableHighlightingOfNonBasicAsciiCharactersAction.ID, + label: nls.localize('action.unicodeHighlight.dhowDisableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'), + alias: 'Disable highlighting of non basic ASCII characters', + precondition: undefined + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise { + let configurationService = accessor?.get(IConfigurationService); + if (configurationService) { + this.runAction(configurationService); + } + } + + public async runAction(configurationService: IConfigurationService): Promise { + await configurationService.updateValue(unicodeHighlightConfigKeys.nonBasicASCII, false, ConfigurationTarget.USER); + } +} + interface ShowExcludeOptionsArgs { codePoint: number; reason: UnicodeHighlighterReason['kind']; @@ -439,6 +603,7 @@ export class ShowExcludeOptions extends EditorAction { precondition: undefined }); } + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise { const { codePoint, reason } = args as ShowExcludeOptionsArgs; @@ -470,28 +635,16 @@ export class ShowExcludeOptions extends EditorAction { ]; if (reason === UnicodeHighlighterReasonKind.Ambiguous) { - options.push({ - label: nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable highlighting of ambiguous characters'), - run: async () => { - await configurationService.updateValue(unicodeHighlightConfigKeys.ambiguousCharacters, false, ConfigurationTarget.USER); - } - }); + const action = new DisableHighlightingOfAmbiguousCharactersAction(); + options.push({ label: action.label, run: async () => action.runAction(configurationService) }); } else if (reason === UnicodeHighlighterReasonKind.Invisible) { - options.push({ - label: nls.localize('unicodeHighlight.disableHighlightingOfInvisibleCharacters', 'Disable highlighting of invisible characters'), - run: async () => { - await configurationService.updateValue(unicodeHighlightConfigKeys.invisibleCharacters, false, ConfigurationTarget.USER); - } - }); + const action = new DisableHighlightingOfInvisibleCharactersAction(); + options.push({ label: action.label, run: async () => action.runAction(configurationService) }); } else if (reason === UnicodeHighlighterReasonKind.NonBasicAscii) { - options.push({ - label: nls.localize('unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'), - run: async () => { - await configurationService.updateValue(unicodeHighlightConfigKeys.nonBasicASCII, false, ConfigurationTarget.USER); - } - }); + const action = new DisableHighlightingOfNonBasicAsciiCharactersAction(); + options.push({ label: action.label, run: async () => action.runAction(configurationService) }); } else { expectNever(reason); } @@ -511,5 +664,8 @@ function expectNever(value: never) { throw new Error(`Unexpected value: ${value}`); } +registerEditorAction(DisableHighlightingOfAmbiguousCharactersAction); +registerEditorAction(DisableHighlightingOfInvisibleCharactersAction); +registerEditorAction(DisableHighlightingOfNonBasicAsciiCharactersAction); registerEditorAction(ShowExcludeOptions); registerEditorContribution(UnicodeHighlighter.ID, UnicodeHighlighter); diff --git a/src/vs/editor/test/common/mocks/testConfiguration.ts b/src/vs/editor/test/common/mocks/testConfiguration.ts index ec94f2201ce37..044fcae5ac0f6 100644 --- a/src/vs/editor/test/common/mocks/testConfiguration.ts +++ b/src/vs/editor/test/common/mocks/testConfiguration.ts @@ -47,4 +47,8 @@ export class TestConfiguration extends CommonEditorConfiguration { maxDigitWidth: 10, }, true); } + + public reserveHeight(height: number): void { + throw new Error('Not supported'); + } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6792f8509c2f4..a84e7abca3e71 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5117,6 +5117,7 @@ declare namespace monaco.editor { * Apply the same font settings as the editor to `target`. */ applyFontInfo(target: HTMLElement): void; + setBanner(bannerDomNode: HTMLElement | null, height: number): void; } /** diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index d41752d564cfa..eff636e571d4c 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -132,7 +132,7 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { IWorkingCopyEditorService, WorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; import { BrowserElevatedFileService } from 'vs/workbench/services/files/browser/elevatedFileService'; -import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService'; import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/modes'; import { ResourceMap } from 'vs/base/common/map'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; @@ -1866,7 +1866,7 @@ export class TestEditorWorkerService implements IEditorWorkerService { declare readonly _serviceBrand: undefined; canComputeUnicodeHighlights(uri: URI): boolean { return false; } - async computedUnicodeHighlights(uri: URI): Promise { return []; } + async computedUnicodeHighlights(uri: URI): Promise { return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; } async computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { return null; } canComputeDirtyDiff(original: URI, modified: URI): boolean { return false; } async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { return null; } From 75d09de70501471db9a7ab044ed2e732021b5c87 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 26 Nov 2021 10:55:17 +0100 Subject: [PATCH 0068/2210] Moves reservedHeight computation into commonEditorConfig. --- src/vs/editor/browser/config/configuration.ts | 8 +------- src/vs/editor/common/config/commonEditorConfig.ts | 8 ++++++-- src/vs/editor/test/common/mocks/testConfiguration.ts | 4 ---- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index 560e591a87874..f4175c0bf63a5 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -314,7 +314,6 @@ export class Configuration extends CommonEditorConfiguration { } private readonly _elementSizeObserver: ElementSizeObserver; - private _reservedHeight: number = 0; constructor( isSimpleWidget: boolean, @@ -366,7 +365,7 @@ export class Configuration extends CommonEditorConfiguration { return { extraEditorClassName: Configuration._getExtraEditorClassName(), outerWidth: this._elementSizeObserver.getWidth(), - outerHeight: this._elementSizeObserver.getHeight() - this._reservedHeight, + outerHeight: this._elementSizeObserver.getHeight(), emptySelectionClipboard: browser.isWebKit || browser.isFirefox, pixelRatio: browser.getPixelRatio(), zoomLevel: browser.getZoomLevel(), @@ -381,9 +380,4 @@ export class Configuration extends CommonEditorConfiguration { protected readConfiguration(bareFontInfo: BareFontInfo): FontInfo { return CSSBasedConfiguration.INSTANCE.readConfiguration(bareFontInfo); } - - public reserveHeight(height: number) { - this._reservedHeight = height; - this._recomputeOptions(); - } } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index b874b8a70f8cc..cbb3314229eef 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -313,6 +313,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC private _rawOptions: IEditorOptions; private _readOptions: RawEditorOptions; protected _validatedOptions: ValidatedEditorOptions; + private _reservedHeight: number = 0; constructor(isSimpleWidget: boolean, _options: Readonly) { super(); @@ -367,7 +368,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC const env: IEnvironmentalOptions = { memory: this._computeOptionsMemory, outerWidth: partialEnv.outerWidth, - outerHeight: partialEnv.outerHeight, + outerHeight: partialEnv.outerHeight - this._reservedHeight, fontInfo: this.readConfiguration(bareFontInfo), extraEditorClassName: partialEnv.extraEditorClassName, isDominatedByLongLines: this._isDominatedByLongLines, @@ -458,7 +459,10 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC protected abstract readConfiguration(styling: BareFontInfo): FontInfo; - public abstract reserveHeight(height: number): void; + public reserveHeight(height: number) { + this._reservedHeight = height; + this._recomputeOptions(); + } } export const editorConfigurationBaseNode = Object.freeze({ diff --git a/src/vs/editor/test/common/mocks/testConfiguration.ts b/src/vs/editor/test/common/mocks/testConfiguration.ts index 044fcae5ac0f6..ec94f2201ce37 100644 --- a/src/vs/editor/test/common/mocks/testConfiguration.ts +++ b/src/vs/editor/test/common/mocks/testConfiguration.ts @@ -47,8 +47,4 @@ export class TestConfiguration extends CommonEditorConfiguration { maxDigitWidth: 10, }, true); } - - public reserveHeight(height: number): void { - throw new Error('Not supported'); - } } From ab8b0b914a224cc99e3ca628d1de4a1e5a5a59f8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 26 Nov 2021 10:56:41 +0100 Subject: [PATCH 0069/2210] Set height to 0 if domNode is null. --- src/vs/editor/browser/widget/codeEditorWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 19b22fd66a629..75756825872f2 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1492,13 +1492,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE Configuration.applyFontInfoSlow(target, this._configuration.options.get(EditorOption.fontInfo)); } - public setBanner(domNode: HTMLElement | null, height: number): void { + public setBanner(domNode: HTMLElement | null, domNodeHeight: number): void { if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) { this._domElement.removeChild(this._bannerDomNode); } this._bannerDomNode = domNode; - this._configuration.reserveHeight(height); + this._configuration.reserveHeight(domNode ? domNodeHeight : 0); if (this._bannerDomNode) { this._domElement.prepend(this._bannerDomNode); From 8a305e17d5514a4147f75b7dd476858c52a82a12 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 26 Nov 2021 10:58:35 +0100 Subject: [PATCH 0070/2210] Introduces interface to keep shortLabel alive. --- .../contrib/unicodeHighlighter/unicodeHighlighter.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index 5650ff6e2fb93..d0cf24627c211 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -516,7 +516,11 @@ const DECORATION = ModelDecorationOptions.register({ } }); -export class DisableHighlightingOfAmbiguousCharactersAction extends EditorAction { +interface IDisableUnicodeHighlightAction { + shortLabel: string; +} + +export class DisableHighlightingOfAmbiguousCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction { public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters'; public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters.shortLabel', ''); constructor() { @@ -540,7 +544,7 @@ export class DisableHighlightingOfAmbiguousCharactersAction extends EditorAction } } -export class DisableHighlightingOfInvisibleCharactersAction extends EditorAction { +export class DisableHighlightingOfInvisibleCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction { public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfInvisibleCharacters'; public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfInvisibleCharacters.shortLabel', 'Disable Invisible Highlight'); constructor() { @@ -564,7 +568,7 @@ export class DisableHighlightingOfInvisibleCharactersAction extends EditorAction } } -export class DisableHighlightingOfNonBasicAsciiCharactersAction extends EditorAction { +export class DisableHighlightingOfNonBasicAsciiCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction { public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters'; public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters.shortLabel', 'Disable Non ASCII Highlight'); constructor() { From 5ce5e6cc0274691620367b71e9bcc40ac3b25d55 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Nov 2021 11:22:38 +0100 Subject: [PATCH 0071/2210] tests - really skip test (#137853) --- .../contrib/testing/test/common/testResultService.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index 49965171530a5..925909fe0f45b 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -312,11 +312,7 @@ suite('Workbench - Test Results Service', () => { }); }); - test('resultItemParents', function () { - if (isNative && !isElectron) { - this.skip(); // TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 - } - + ((isNative && !isElectron) ? test.skip /* TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 */ : test)('resultItemParents', function () { assert.deepStrictEqual([...resultItemParents(r, r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString())!)], [ r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString()), r.getStateById(new TestId(['ctrlId', 'id-a']).toString()), From a8b571c9f396177222c1cb4c38469d1428037ad7 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 26 Nov 2021 11:28:44 +0100 Subject: [PATCH 0072/2210] :up: `native-keymap` --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1f34faf5c2f7c..bc145ad8c0b15 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "keytar": "7.2.0", "minimist": "^1.2.5", "native-is-elevated": "0.4.3", - "native-keymap": "3.0.1", + "native-keymap": "3.0.2", "native-watchdog": "1.3.0", "node-pty": "0.11.0-beta11", "spdlog": "^0.13.0", diff --git a/yarn.lock b/yarn.lock index b6b77dee32613..ecfdc31534d93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6903,10 +6903,10 @@ native-is-elevated@0.4.3: resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.4.3.tgz#f1071c4a821acc71d43f36ff8051d3816d832e1c" integrity sha512-bHS3sCoh+raqFGIxmL/plER3eBQ+IEBy4RH/4uahhToZneTvqNKQrL0PgOTtnpL55XjBd3dy0pNtZMkCk0J48g== -native-keymap@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-3.0.1.tgz#7cc2d30da1e60cbb7d599423e05cb84845d20a8f" - integrity sha512-IeHaz5NM1mF3AKIwBxf4YhgrB/hcctVwIqOXaMrR8Hz8v45dCa364YDvEN0004zSycRyhrXh6cNgCQ/v6QUHkA== +native-keymap@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-3.0.2.tgz#eb299a9f1a252be552ee2345ba8a39ff4b2c304a" + integrity sha512-1qH2saFkYzwT+c2a1xaawrfgkiT1OJ6H8xZWx9zmhhOkuJto/HaNLSINxCYhSjc7zO0zOydMkoCXehGh3TAWcg== native-watchdog@1.3.0: version "1.3.0" From 6b2aa3abfda6255ae89335bdc27d907a26ddb957 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 26 Nov 2021 11:41:50 +0100 Subject: [PATCH 0073/2210] allow to mark a language status item as busy, https://github.com/microsoft/vscode/issues/129037 --- src/vs/workbench/api/common/extHostLanguages.ts | 11 ++++++++++- .../browser/languageStatus.contribution.ts | 8 +++++--- .../languageStatus/common/languageStatusService.ts | 1 + src/vscode-dts/vscode.proposed.languageStatus.d.ts | 1 + 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguages.ts b/src/vs/workbench/api/common/extHostLanguages.ts index 6025f37075bdf..9fcf2ee479c5d 100644 --- a/src/vs/workbench/api/common/extHostLanguages.ts +++ b/src/vs/workbench/api/common/extHostLanguages.ts @@ -98,6 +98,7 @@ export class ExtHostLanguages implements ExtHostLanguagesShape { command: undefined, text: '', detail: '', + busy: false }; let soonHandle: IDisposable | undefined; @@ -115,7 +116,8 @@ export class ExtHostLanguages implements ExtHostLanguagesShape { detail: data.detail ?? '', severity: data.severity === LanguageStatusSeverity.Error ? Severity.Error : data.severity === LanguageStatusSeverity.Warning ? Severity.Warning : Severity.Info, command: data.command && this._commands.toInternal(data.command, commandDisposables), - accessibilityInfo: data.accessibilityInformation + accessibilityInfo: data.accessibilityInformation, + busy: data.busy }); }, 0); }; @@ -178,6 +180,13 @@ export class ExtHostLanguages implements ExtHostLanguagesShape { set command(value) { data.command = value; updateAsync(); + }, + get busy() { + return data.busy; + }, + set busy(value: boolean) { + data.busy = value; + updateAsync(); } }; updateAsync(); diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 41967cf8bf7a3..a52b8b898b9d1 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -160,18 +160,20 @@ class EditorStatusContribution implements IWorkbenchContribution { const showSeverity = first.severity >= Severity.Warning; const text = EditorStatusContribution._severityToComboCodicon(first.severity); + let isOneBusy = false; const ariaLabels: string[] = []; const element = document.createElement('div'); for (const status of model.combined) { element.appendChild(this._renderStatus(status, showSeverity, this._renderDisposables)); ariaLabels.push(this._asAriaLabel(status)); + isOneBusy = isOneBusy || status.busy; } const props: IStatusbarEntry = { name: localize('langStatus.name', "Editor Language Status"), ariaLabel: localize('langStatus.aria', "Editor Language Status: {0}", ariaLabels.join(', next: ')), tooltip: element, command: ShowTooltipCommand, - text, + text: isOneBusy ? `${text}\u00A0\u00A0$(sync~spin)` : text, }; if (!this._combinedEntry) { this._combinedEntry = this._statusBarService.addEntry(props, EditorStatusContribution._id, StatusbarAlignment.RIGHT, { id: 'status.editor.mode', alignment: StatusbarAlignment.LEFT, compact: true }); @@ -219,7 +221,7 @@ class EditorStatusContribution implements IWorkbenchContribution { const label = document.createElement('span'); label.classList.add('label'); - dom.append(label, ...renderLabelWithIcons(status.label)); + dom.append(label, ...renderLabelWithIcons(status.busy ? `$(sync~spin)\u00A0\u00A0${status.label}` : status.label)); left.appendChild(label); const detail = document.createElement('span'); @@ -311,7 +313,7 @@ class EditorStatusContribution implements IWorkbenchContribution { return { name: localize('name.pattern', '{0} (Language Status)', item.name), - text: item.label, + text: item.busy ? `${item.label}\u00A0\u00A0$(sync~spin)` : item.label, ariaLabel: item.accessibilityInfo?.label ?? item.label, role: item.accessibilityInfo?.role, tooltip: item.command?.tooltip || new MarkdownString(item.detail, true), diff --git a/src/vs/workbench/services/languageStatus/common/languageStatusService.ts b/src/vs/workbench/services/languageStatus/common/languageStatusService.ts index e98997649334f..ec5ac2c7f15b0 100644 --- a/src/vs/workbench/services/languageStatus/common/languageStatusService.ts +++ b/src/vs/workbench/services/languageStatus/common/languageStatusService.ts @@ -23,6 +23,7 @@ export interface ILanguageStatus { readonly severity: Severity; readonly label: string; readonly detail: string; + readonly busy: boolean; readonly source: string; readonly command: Command | undefined; readonly accessibilityInfo: IAccessibilityInformation | undefined; diff --git a/src/vscode-dts/vscode.proposed.languageStatus.d.ts b/src/vscode-dts/vscode.proposed.languageStatus.d.ts index 0249626162b9f..9de4ed5309e07 100644 --- a/src/vscode-dts/vscode.proposed.languageStatus.d.ts +++ b/src/vscode-dts/vscode.proposed.languageStatus.d.ts @@ -18,6 +18,7 @@ declare module 'vscode' { selector: DocumentSelector; // todo@jrieken replace with boolean ala needsAttention severity: LanguageStatusSeverity; + busy: boolean; name: string | undefined; text: string; detail?: string; From f1455eabedfea1ae02bd68351b1c910896551371 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 26 Nov 2021 11:45:08 +0100 Subject: [PATCH 0074/2210] revert request changes & polish --- .../client/src/customData.ts | 36 ++++++++------- .../client/src/htmlClient.ts | 2 +- .../client/src/requests.ts | 45 ++++++++----------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index 146fef2f3ee64..02c5b0d0a7d08 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode'; -import { resolvePath, joinPath, uriScheme } from './requests'; +import { resolvePath, joinPath } from './requests'; export function getCustomDataSource(toDispose: Disposable[]) { @@ -44,6 +44,10 @@ export function getCustomDataSource(toDispose: Disposable[]) { }; } +function isURI(uriOrPath: string) { + return /^(?\w[\w\d+.-]*):/.test(uriOrPath); +} + function getCustomDataPathsInAllWorkspaces(): Set { const workspaceFolders = workspace.workspaceFolders; @@ -54,16 +58,16 @@ function getCustomDataPathsInAllWorkspaces(): Set { return dataPaths; } - const collect = (paths: string[] | undefined, rootFolder: Uri) => { - if (Array.isArray(paths)) { - for (const path of paths) { - if (typeof path === 'string') { - if (!uriScheme.test(path)) { - // only resolve file paths relative to extension - dataPaths.add(resolvePath(rootFolder, path).toString()); + const collect = (uriOrPaths: string[] | undefined, rootFolder: Uri) => { + if (Array.isArray(uriOrPaths)) { + for (const uriOrPath of uriOrPaths) { + if (typeof uriOrPath === 'string') { + if (!isURI(uriOrPath)) { + // path in the workspace + dataPaths.add(resolvePath(rootFolder, uriOrPath).toString()); } else { - // others schemes - dataPaths.add(path); + // external uri + dataPaths.add(uriOrPath); } } } @@ -93,13 +97,13 @@ function getCustomDataPathsFromAllExtensions(): Set { for (const extension of extensions.all) { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { - for (const rp of customData) { - if (!uriScheme.test(rp)) { - // no schame -> resolve relative to extension - dataPaths.add(joinPath(extension.extensionUri, rp).toString()); + for (const uriOrPath of customData) { + if (!isURI(uriOrPath)) { + // relative path in an extension + dataPaths.add(joinPath(extension.extensionUri, uriOrPath).toString()); } else { - // actual schemes - dataPaths.add(rp); + // external uri + dataPaths.add(uriOrPath); } } diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 3e7ca38be7ea9..bfb241c22506a 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -120,7 +120,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua toDispose.push(disposable); client.onReady().then(() => { - serveFileSystemRequests(client, runtime, context.subscriptions); + toDispose.push(serveFileSystemRequests(client, runtime)); client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); customDataSource.onDidChange(() => { diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index aa75e6b615f2c..867ad6fc7d442 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -3,12 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, workspace } from 'vscode'; +import { Uri, workspace, Disposable } from 'vscode'; import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; import { Runtime } from './htmlClient'; -export const uriScheme = /^(?\w[\w\d+.-]*):/; - export namespace FsContentRequest { export const type: RequestType<{ uri: string; encoding?: string; }, string, any> = new RequestType('fs/content'); } @@ -20,37 +18,32 @@ export namespace FsReadDirRequest { export const type: RequestType = new RequestType('fs/readDir'); } -export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime, subscriptions: { dispose(): any }[]) { - subscriptions.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { - const uri = param.uri.match(uriScheme); - if (uri?.groups?.scheme === 'file') { - if (runtime.fs) { - return runtime.fs.getContent(param.uri); - } else { - return workspace.fs.readFile(Uri.parse(param.uri)).then(buffer => { - return new runtime.TextDecoder(param.encoding).decode(buffer); - }); - } - } else { - return workspace.openTextDocument(Uri.parse(param.uri)).then(doc => { - return doc.getText(); - }); +export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime): Disposable { + const disposables = []; + disposables.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { + const uri = Uri.parse(param.uri); + if (uri.scheme === 'file' && runtime.fs) { + return runtime.fs.getContent(param.uri); } + return workspace.fs.readFile(uri).then(buffer => { + return new runtime.TextDecoder(param.encoding).decode(buffer); + }); })); - subscriptions.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { - const uri = uriString.match(uriScheme); - if (uri?.groups?.scheme === 'file' && runtime.fs) { + disposables.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { + const uri = Uri.parse(uriString); + if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.readDirectory(uriString); } - return workspace.fs.readDirectory(Uri.parse(uriString)); + return workspace.fs.readDirectory(uri); })); - subscriptions.push(client.onRequest(FsStatRequest.type, (uriString: string) => { - const uri = uriString.match(uriScheme); - if (uri?.groups?.scheme === 'file' && runtime.fs) { + disposables.push(client.onRequest(FsStatRequest.type, (uriString: string) => { + const uri = Uri.parse(uriString); + if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.stat(uriString); } - return workspace.fs.stat(Uri.parse(uriString)); + return workspace.fs.stat(uri); })); + return Disposable.from(...disposables); } export enum FileType { From 22a1d0b1d21bda17a656678a29592276257bdb39 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 26 Nov 2021 11:51:33 +0100 Subject: [PATCH 0075/2210] Fixes presentation of code point. --- src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index d0cf24627c211..ec9ea5542aa98 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -448,7 +448,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa reason = nls.localize( 'unicodeHighlight.characterIsNonBasicAscii', 'The character {0} is not a basic ASCII character.', - codePoint + codePointStr ); break; } From d673cdb0ecf1c760f22e0ba5530971e1d91a9d44 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 26 Nov 2021 12:20:00 +0100 Subject: [PATCH 0076/2210] some jsdoc for language status items, https://github.com/microsoft/vscode/issues/129037 --- .../vscode.proposed.languageStatus.d.ts | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.languageStatus.d.ts b/src/vscode-dts/vscode.proposed.languageStatus.d.ts index 9de4ed5309e07..d39081e820257 100644 --- a/src/vscode-dts/vscode.proposed.languageStatus.d.ts +++ b/src/vscode-dts/vscode.proposed.languageStatus.d.ts @@ -14,16 +14,59 @@ declare module 'vscode' { } interface LanguageStatusItem { + + /** + * The identifier of this item. + */ readonly id: string; + + /** + * The short name of this item, like 'Java Language Status', etc. + */ + name: string | undefined; + + /** + * A {@link DocumentSelector selector} that defines for what documents + * this item shows. + */ selector: DocumentSelector; + // todo@jrieken replace with boolean ala needsAttention severity: LanguageStatusSeverity; - busy: boolean; - name: string | undefined; + + /** + * The text to show for the entry. You can embed icons in the text by leveraging the syntax: + * + * `My text $(icon-name) contains icons like $(icon-name) this one.` + * + * Where the icon-name is taken from the ThemeIcon [icon set](https://code.visualstudio.com/api/references/icons-in-labels#icon-listing), e.g. + * `light-bulb`, `thumbsup`, `zap` etc. + */ text: string; + + /** + * Optional, human-readable details for this item. + */ detail?: string; + + /** + * Controls whether the item is shown as "busy". Defaults to `false`. + */ + busy: boolean; + + /** + * A {@linkcode Command command} for this item. + */ command: Command | undefined; + + /** + * Accessibility information used when a screen reader interacts with this item + */ accessibilityInformation?: AccessibilityInformation; + + /** + * Dispose and free associated resources. + */ dispose(): void; } From 39c6132b2c4a07efcc69627c18b88fb60b34a091 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 13:35:01 +0100 Subject: [PATCH 0077/2210] #134684 override default values from workbenchAssignmentsService for experimental settings --- .../common/configurationRegistry.ts | 11 +++++ .../browser/configurationService.ts | 41 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index bd3b22eca92bc..d8bc19618b864 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -136,8 +136,16 @@ export interface IConfigurationPropertySchema extends IJSONSchema { */ restricted?: boolean; + /** + * When `false` this property is excluded from the registry. Default is to include. + */ included?: boolean; + /** + * List of tags associated to the property. + * - A tag can be used for filtering + * - Use `experimental` tag for marking the setting as experimental. **Note:** Defaults of experimental settings can be changed by the running experiments. + */ tags?: string[]; /** @@ -150,6 +158,9 @@ export interface IConfigurationPropertySchema extends IJSONSchema { */ disallowSyncIgnore?: boolean; + /** + * Labels for enumeration items + */ enumItemLabels?: string[]; /** diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 04dab49ec07c3..083809def009c 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -36,6 +36,8 @@ import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/w import { delta, distinct } from 'vs/base/common/arrays'; import { forEach, IStringDictionary } from 'vs/base/common/collections'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; +import { isUndefined } from 'vs/base/common/types'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -1116,6 +1118,45 @@ class ResetConfigurationDefaultsOverridesCache extends Disposable implements IWo } } +class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenchContribution { + + private readonly processedExperimentalSettings = new Set(); + private readonly configurationRegistry = Registry.as(Extensions.Configuration); + + constructor( + @IWorkbenchAssignmentService private readonly workbenchAssignmentService: IWorkbenchAssignmentService + ) { + super(); + this.processExperimentalSettings(Object.keys(this.configurationRegistry.getConfigurationProperties())); + this._register(this.configurationRegistry.onDidUpdateConfiguration(({ properties }) => this.processExperimentalSettings(properties))); + } + + private async processExperimentalSettings(properties: string[]): Promise { + const overrides: IStringDictionary = {}; + const allProperties = this.configurationRegistry.getConfigurationProperties(); + for (const property of properties) { + const schema = allProperties[property]; + if (!schema.tags?.includes('experimental')) { + continue; + } + if (this.processedExperimentalSettings.has(property)) { + continue; + } + this.processedExperimentalSettings.add(property); + try { + const value = await this.workbenchAssignmentService.getTreatment(`config.${property}`); + if (!isUndefined(value) && !equals(value, schema.default)) { + overrides[property] = value; + } + } catch (error) {/*ignore */ } + } + if (Object.keys(overrides).length) { + this.configurationRegistry.registerDefaultConfigurations([{ overrides }]); + } + } +} + const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(ResetConfigurationDefaultsOverridesCache, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution(UpdateExperimentalSettingsDefaults, LifecyclePhase.Restored); From 49fc9c109ead78f90a5030c342afedcd13e0ab40 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 14:02:48 +0100 Subject: [PATCH 0078/2210] #46851 pass extension description as source --- .../common/configurationRegistry.ts | 28 +++++++++++-------- .../api/common/configurationExtensionPoint.ts | 6 ++-- .../preferences/browser/settingsTreeModels.ts | 2 +- .../browser/configurationService.ts | 3 +- .../preferences/common/preferencesModels.ts | 2 +- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index d8bc19618b864..55f4c5a72b17e 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -9,6 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as types from 'vs/base/common/types'; import * as nls from 'vs/nls'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -177,7 +178,7 @@ export interface IConfigurationPropertySchema extends IJSONSchema { } export interface IConfigurationExtensionInfo { - id: string; + extensionDescription: IExtensionDescription; restrictedConfigurations?: string[]; } @@ -195,16 +196,16 @@ export interface IConfigurationNode { export interface IConfigurationDefaults { overrides: IStringDictionary; - extensionId?: string; + source?: IExtensionDescription | string; } export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & { defaultDefaultValue?: any, - source?: string, - defaultSource?: string + source?: IExtensionDescription, + defaultSource?: IExtensionDescription | string; }; -export type IConfigurationDefaultOverride = { value: any, source?: string }; +export type IConfigurationDefaultOverride = { value: any, source?: IExtensionDescription | string }; export const allSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const applicationSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; @@ -283,26 +284,26 @@ class ConfigurationRegistry implements IConfigurationRegistry { const properties: string[] = []; const overrideIdentifiers: string[] = []; - for (const { overrides, extensionId } of configurationDefaults) { + for (const { overrides, source } of configurationDefaults) { for (const key in overrides) { properties.push(key); if (OVERRIDE_PROPERTY_REGEX.test(key)) { const defaultValue = { ...(this.configurationDefaultsOverrides.get(key)?.value || {}), ...overrides[key] }; - this.configurationDefaultsOverrides.set(key, { source: extensionId, value: defaultValue }); + this.configurationDefaultsOverrides.set(key, { source, value: defaultValue }); const property: IRegisteredConfigurationPropertySchema = { type: 'object', default: defaultValue, description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key), $ref: resourceLanguageSettingsSchemaId, defaultDefaultValue: defaultValue, - source: extensionId, + source: types.isString(source) ? undefined : source, }; overrideIdentifiers.push(...overrideIdentifiersFromKey(key)); this.configurationProperties[key] = property; this.defaultLanguageConfigurationOverridesNode.properties![key] = property; } else { - this.configurationDefaultsOverrides.set(key, { value: overrides[key], source: extensionId }); + this.configurationDefaultsOverrides.set(key, { value: overrides[key], source }); const property = this.configurationProperties[key]; if (property) { this.updatePropertyDefaultValue(key, property); @@ -319,9 +320,12 @@ class ConfigurationRegistry implements IConfigurationRegistry { public deregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[]): void { const properties: string[] = []; - for (const { overrides, extensionId } of defaultConfigurations) { + for (const { overrides, source } of defaultConfigurations) { for (const key in overrides) { - if (this.configurationDefaultsOverrides.get(key)?.source !== extensionId) { + const configurationDefaultsOverride = this.configurationDefaultsOverrides.get(key); + const id = types.isString(source) ? source : source?.identifier.value; + const configurationDefaultsOverrideSourceId = types.isString(configurationDefaultsOverride?.source) ? configurationDefaultsOverride?.source : configurationDefaultsOverride?.source?.identifier.value; + if (id !== configurationDefaultsOverrideSourceId) { continue; } properties.push(key); @@ -401,7 +405,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } const property: IRegisteredConfigurationPropertySchema = properties[key]; - property.source = extensionInfo?.id; + property.source = extensionInfo?.extensionDescription; // update default value property.defaultDefaultValue = properties[key].default; diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index d59ced8280dd2..18d70fbddc5e5 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -144,7 +144,7 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { if (removed.length) { - const removedDefaultConfigurations = removed.map(extension => ({ overrides: objects.deepClone(extension.value), extensionId: extension.description.identifier.value })); + const removedDefaultConfigurations = removed.map(extension => ({ overrides: objects.deepClone(extension.value), source: extension.description })); configurationRegistry.deregisterDefaultConfigurations(removedDefaultConfigurations); } if (added.length) { @@ -161,7 +161,7 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => { } } } - return { overrides, extensionId: extension.description.identifier.value }; + return { overrides, source: extension.description }; }); configurationRegistry.registerDefaultConfigurations(addedDefaultConfigurations); } @@ -212,7 +212,7 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { validateProperties(configuration, extension); configuration.id = node.id || extension.description.identifier.value; - configuration.extensionInfo = { id: extension.description.identifier.value, restrictedConfigurations: extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined }; + configuration.extensionInfo = { extensionDescription: extension.description, restrictedConfigurations: extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined }; configuration.title = configuration.title || extension.description.displayName || extension.description.identifier.value; configurations.push(configuration); return configurations; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 27b1cbbc00b39..f257c9cbfc36f 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -314,7 +314,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { return false; } - return Array.from(extensionFilters).some(extensionId => extensionId.toLowerCase() === this.setting.extensionInfo!.id.toLowerCase()); + return Array.from(extensionFilters).some(extensionId => extensionId.toLowerCase() === this.setting.extensionInfo!.extensionDescription.identifier.value.toLowerCase()); } matchesAnyFeature(featureFilters?: Set): boolean { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 083809def009c..2dca97e623d18 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -38,6 +38,7 @@ import { forEach, IStringDictionary } from 'vs/base/common/collections'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { isUndefined } from 'vs/base/common/types'; +import { localize } from 'vs/nls'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -1151,7 +1152,7 @@ class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenc } catch (error) {/*ignore */ } } if (Object.keys(overrides).length) { - this.configurationRegistry.registerDefaultConfigurations([{ overrides }]); + this.configurationRegistry.registerDefaultConfigurations([{ overrides, source: localize('experimental', "Experiments") }]); } } } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 8579000624ee5..d3558a376e587 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -565,7 +565,7 @@ export class DefaultSettings extends Disposable { } if (title) { if (!settingsGroup) { - settingsGroup = result.find(g => g.title === title && g.extensionInfo?.id === config.extensionInfo?.id); + settingsGroup = result.find(g => g.title === title && g.extensionInfo?.extensionDescription.identifier.value === config.extensionInfo?.extensionDescription.identifier.value); if (!settingsGroup) { settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: title || '', titleRange: nullRange, order: config.order, range: nullRange, extensionInfo: config.extensionInfo }; result.push(settingsGroup); From 5cb1766018d764470c11c81c5a69be9c111f3da4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 14:09:12 +0100 Subject: [PATCH 0079/2210] #46851 pass extension description as source - adopt at other places --- src/vs/workbench/contrib/preferences/browser/settingsTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index d15f9878eab88..a4c61f01f1146 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -422,7 +422,7 @@ export async function resolveExtensionsSettings(extensionService: IExtensionServ const flatSettings = arrays.flatten( group.sections.map(section => section.settings)); - const extensionId = group.extensionInfo!.id; + const extensionId = group.extensionInfo!.extensionDescription.identifier.value; const extension = await extensionService.getExtension(extensionId); const extensionName = extension!.displayName ?? extension!.name; From 13ff6baa3fea229c4a4844ae7c31a9398a4ebe5c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Nov 2021 14:45:35 +0100 Subject: [PATCH 0080/2210] fix smoke tests --- test/smoke/src/areas/workbench/data-migration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 45d851727961b..d95a7c3e3a2b2 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -170,7 +170,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) { await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); await insidersApp.workbench.editors.waitForTab(untitled, true); - await insidersApp.workbench.quickaccess.openFile(textToTypeInUntitled); + await insidersApp.workbench.quickaccess.openFile(untitled, textToTypeInUntitled); await insidersApp.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); await insidersApp.stop(); From 212deea1ccf107e9e1a167b7b26ceac6f270f448 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 14:54:08 +0100 Subject: [PATCH 0081/2210] #46851 passing extension description breaks intellisense as it is not serializable. Pass only id and display name as extension info --- .../common/configurationRegistry.ts | 32 +++++++++---------- .../api/common/configurationExtensionPoint.ts | 7 ++-- .../preferences/browser/settingsTree.ts | 2 +- .../preferences/browser/settingsTreeModels.ts | 2 +- .../preferences/common/preferences.ts | 6 ++-- .../preferences/common/preferencesModels.ts | 6 ++-- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 55f4c5a72b17e..eda635aaeaf43 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -9,7 +9,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as types from 'vs/base/common/types'; import * as nls from 'vs/nls'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -177,9 +176,9 @@ export interface IConfigurationPropertySchema extends IJSONSchema { order?: number; } -export interface IConfigurationExtensionInfo { - extensionDescription: IExtensionDescription; - restrictedConfigurations?: string[]; +export interface IExtensionInfo { + id: string; + displayName?: string; } export interface IConfigurationNode { @@ -191,21 +190,22 @@ export interface IConfigurationNode { properties?: IStringDictionary; allOf?: IConfigurationNode[]; scope?: ConfigurationScope; - extensionInfo?: IConfigurationExtensionInfo; + extensionInfo?: IExtensionInfo; + restrictedProperties?: string[]; } export interface IConfigurationDefaults { overrides: IStringDictionary; - source?: IExtensionDescription | string; + source?: IExtensionInfo | string; } export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & { defaultDefaultValue?: any, - source?: IExtensionDescription, - defaultSource?: IExtensionDescription | string; + source?: IExtensionInfo, + defaultSource?: IExtensionInfo | string; }; -export type IConfigurationDefaultOverride = { value: any, source?: IExtensionDescription | string }; +export type IConfigurationDefaultOverride = { value: any, source?: IExtensionInfo | string }; export const allSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const applicationSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; @@ -323,8 +323,8 @@ class ConfigurationRegistry implements IConfigurationRegistry { for (const { overrides, source } of defaultConfigurations) { for (const key in overrides) { const configurationDefaultsOverride = this.configurationDefaultsOverrides.get(key); - const id = types.isString(source) ? source : source?.identifier.value; - const configurationDefaultsOverrideSourceId = types.isString(configurationDefaultsOverride?.source) ? configurationDefaultsOverride?.source : configurationDefaultsOverride?.source?.identifier.value; + const id = types.isString(source) ? source : source?.id; + const configurationDefaultsOverrideSourceId = types.isString(configurationDefaultsOverride?.source) ? configurationDefaultsOverride?.source : configurationDefaultsOverride?.source?.id; if (id !== configurationDefaultsOverrideSourceId) { continue; } @@ -362,7 +362,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { private doRegisterConfigurations(configurations: IConfigurationNode[], validate: boolean): string[] { const properties: string[] = []; configurations.forEach(configuration => { - properties.push(...this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo)); // fills in defaults + properties.push(...this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo, configuration.restrictedProperties)); // fills in defaults this.configurationContributors.push(configuration); this.registerJSONConfiguration(configuration); }); @@ -393,7 +393,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { return properties; } - private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, extensionInfo?: IConfigurationExtensionInfo, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] { + private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, extensionInfo: IExtensionInfo | undefined, restrictedProperties: string[] | undefined, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] { scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope; let propertyKeys: string[] = []; let properties = configuration.properties; @@ -405,7 +405,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } const property: IRegisteredConfigurationPropertySchema = properties[key]; - property.source = extensionInfo?.extensionDescription; + property.source = extensionInfo; // update default value property.defaultDefaultValue = properties[key].default; @@ -416,7 +416,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { property.scope = undefined; // No scope for overridable properties `[${identifier}]` } else { property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope; - property.restricted = types.isUndefinedOrNull(property.restricted) ? !!extensionInfo?.restrictedConfigurations?.includes(key) : property.restricted; + property.restricted = types.isUndefinedOrNull(property.restricted) ? !!restrictedProperties?.includes(key) : property.restricted; } // Add to properties maps @@ -440,7 +440,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { let subNodes = configuration.allOf; if (subNodes) { for (let node of subNodes) { - propertyKeys.push(...this.validateAndRegisterProperties(node, validate, extensionInfo, scope)); + propertyKeys.push(...this.validateAndRegisterProperties(node, validate, extensionInfo, restrictedProperties, scope)); } } return propertyKeys; diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 18d70fbddc5e5..f1e7bff0d3520 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -144,7 +144,7 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { if (removed.length) { - const removedDefaultConfigurations = removed.map(extension => ({ overrides: objects.deepClone(extension.value), source: extension.description })); + const removedDefaultConfigurations = removed.map(extension => ({ overrides: objects.deepClone(extension.value), source: { id: extension.description.identifier.value, displayName: extension.description.displayName } })); configurationRegistry.deregisterDefaultConfigurations(removedDefaultConfigurations); } if (added.length) { @@ -161,7 +161,7 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => { } } } - return { overrides, source: extension.description }; + return { overrides, source: { id: extension.description.identifier.value, displayName: extension.description.displayName } }; }); configurationRegistry.registerDefaultConfigurations(addedDefaultConfigurations); } @@ -212,7 +212,8 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { validateProperties(configuration, extension); configuration.id = node.id || extension.description.identifier.value; - configuration.extensionInfo = { extensionDescription: extension.description, restrictedConfigurations: extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined }; + configuration.extensionInfo = { id: extension.description.identifier.value, displayName: extension.description.displayName }; + configuration.restrictedProperties = extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined; configuration.title = configuration.title || extension.description.displayName || extension.description.identifier.value; configurations.push(configuration); return configurations; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index a4c61f01f1146..d15f9878eab88 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -422,7 +422,7 @@ export async function resolveExtensionsSettings(extensionService: IExtensionServ const flatSettings = arrays.flatten( group.sections.map(section => section.settings)); - const extensionId = group.extensionInfo!.extensionDescription.identifier.value; + const extensionId = group.extensionInfo!.id; const extension = await extensionService.getExtension(extensionId); const extensionName = extension!.displayName ?? extension!.name; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index f257c9cbfc36f..27b1cbbc00b39 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -314,7 +314,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { return false; } - return Array.from(extensionFilters).some(extensionId => extensionId.toLowerCase() === this.setting.extensionInfo!.extensionDescription.identifier.value.toLowerCase()); + return Array.from(extensionFilters).some(extensionId => extensionId.toLowerCase() === this.setting.extensionInfo!.id.toLowerCase()); } matchesAnyFeature(featureFilters?: Set): boolean { diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index a9026ea613146..2857413256bb7 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, EditPresentationTypes, IConfigurationExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, EditPresentationTypes, IExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry'; import { EditorResolution, IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -44,7 +44,7 @@ export interface ISettingsGroup { titleRange: IRange; sections: ISettingsSection[]; order?: number; - extensionInfo?: IConfigurationExtensionInfo; + extensionInfo?: IExtensionInfo; } export interface ISettingsSection { @@ -81,7 +81,7 @@ export interface ISetting { tags?: string[]; disallowSyncIgnore?: boolean; restricted?: boolean; - extensionInfo?: IConfigurationExtensionInfo; + extensionInfo?: IExtensionInfo; validator?: (value: any) => string | null; enumItemLabels?: string[]; allKeysAreBoolean?: boolean; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index d3558a376e587..87a08ffaaeb7c 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -15,7 +15,7 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IConfigurationExtensionInfo, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IExtensionInfo, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; @@ -565,7 +565,7 @@ export class DefaultSettings extends Disposable { } if (title) { if (!settingsGroup) { - settingsGroup = result.find(g => g.title === title && g.extensionInfo?.extensionDescription.identifier.value === config.extensionInfo?.extensionDescription.identifier.value); + settingsGroup = result.find(g => g.title === title && g.extensionInfo?.id === config.extensionInfo?.id); if (!settingsGroup) { settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: title || '', titleRange: nullRange, order: config.order, range: nullRange, extensionInfo: config.extensionInfo }; result.push(settingsGroup); @@ -607,7 +607,7 @@ export class DefaultSettings extends Disposable { return result; } - private parseSettings(settingsObject: { [path: string]: IConfigurationPropertySchema; }, extensionInfo?: IConfigurationExtensionInfo): ISetting[] { + private parseSettings(settingsObject: { [path: string]: IConfigurationPropertySchema; }, extensionInfo?: IExtensionInfo): ISetting[] { const result: ISetting[] = []; for (const key in settingsObject) { const prop = settingsObject[key]; From 153a028f3bd522d5c2d03e3c346f766cc992e43f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 15:08:33 +0100 Subject: [PATCH 0082/2210] rename --- src/vs/platform/configuration/common/configurationRegistry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index eda635aaeaf43..1083115b16c62 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -202,7 +202,7 @@ export interface IConfigurationDefaults { export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & { defaultDefaultValue?: any, source?: IExtensionInfo, - defaultSource?: IExtensionInfo | string; + defaultValueSource?: IExtensionInfo | string; }; export type IConfigurationDefaultOverride = { value: any, source?: IExtensionInfo | string }; @@ -575,7 +575,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { defaultValue = getDefaultValue(property.type); } property.default = defaultValue; - property.defaultSource = defaultSource; + property.defaultValueSource = defaultSource; } } From 319529dc9db71b633cfe3dfebf8b13bd3529ca30 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 26 Nov 2021 11:51:54 +0100 Subject: [PATCH 0083/2210] Disable highlighting ambiguous characters for markdown. --- extensions/markdown-basics/package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index e40265339c352..f3b99f6606fbc 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -87,7 +87,12 @@ "language": "markdown", "path": "./snippets/markdown.code-snippets" } - ] + ], + "configurationDefaults": { + "[markdown]": { + "editor.unicodeHighlight.ambiguousCharacters": false + } + } }, "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin microsoft/vscode-markdown-tm-grammar syntaxes/markdown.tmLanguage ./syntaxes/markdown.tmLanguage.json" From 40c9c3f677f94debe5ed4adac4c90a77925fcb7a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 26 Nov 2021 11:54:34 +0100 Subject: [PATCH 0084/2210] Disable highlighting ambiguous characters for plaintext. --- src/vs/editor/common/modes/modesRegistry.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/editor/common/modes/modesRegistry.ts b/src/vs/editor/common/modes/modesRegistry.ts index 9971ebcdb7c76..c702ee9dd6e8e 100644 --- a/src/vs/editor/common/modes/modesRegistry.ts +++ b/src/vs/editor/common/modes/modesRegistry.ts @@ -10,6 +10,8 @@ import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; +import { IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; // Define extension point ids export const Extensions = { @@ -86,3 +88,12 @@ LanguageConfigurationRegistry.register(PLAINTEXT_MODE_ID, { offSide: true } }, 0); + +Registry.as(ConfigurationExtensions.Configuration) + .registerDefaultConfigurations([{ + overrides: { + '[plaintext]': { + 'editor.unicodeHighlight.ambiguousCharacters': false + } + } + }]); From e2adc711f3897c6fd76f0a72a316f717b423f00f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 26 Nov 2021 17:39:25 +0100 Subject: [PATCH 0085/2210] Fixes lint error. --- src/vs/editor/common/modes/modesRegistry.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/editor/common/modes/modesRegistry.ts b/src/vs/editor/common/modes/modesRegistry.ts index c702ee9dd6e8e..7eb2238c21277 100644 --- a/src/vs/editor/common/modes/modesRegistry.ts +++ b/src/vs/editor/common/modes/modesRegistry.ts @@ -10,8 +10,7 @@ import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; -import { IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; // Define extension point ids export const Extensions = { From df4b5d6d046c51a7bcbb46f10b5c0d4f8ee43174 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 26 Nov 2021 18:08:23 +0100 Subject: [PATCH 0086/2210] smoke - strengthen shutdown path --- test/automation/src/code.ts | 11 ++++------- test/automation/src/playwrightDriver.ts | 16 +++++++++++----- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index a5ab213ff8474..a48ec0fee138d 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -63,7 +63,7 @@ function getBuildOutPath(root: string): string { } } -async function connect(connectDriver: typeof connectElectronDriver, child: cp.ChildProcess | undefined, outPath: string, handlePath: string, logger: Logger): Promise { +async function connect(connectDriver: typeof connectElectronDriver | typeof connectPlaywrightDriver, child: cp.ChildProcess | undefined, outPath: string, handlePath: string, logger: Logger): Promise { let errCount = 0; while (true) { @@ -79,7 +79,7 @@ async function connect(connectDriver: typeof connectElectronDriver, child: cp.Ch } // retry - await new Promise(c => setTimeout(c, 100)); + await new Promise(resolve => setTimeout(resolve, 100)); } } } @@ -116,14 +116,12 @@ export async function spawn(options: SpawnOptions): Promise { const handle = await createDriverHandle(); let child: cp.ChildProcess | undefined; - let connectDriver: typeof connectElectronDriver; copyExtension(options.extensionsPath, 'vscode-notebook-tests'); if (options.web) { await launch(options.userDataDir, options.workspacePath, options.codePath, options.extensionsPath, Boolean(options.verbose)); - connectDriver = connectPlaywrightDriver.bind(connectPlaywrightDriver, options); - return connect(connectDriver, child, '', handle, options.logger); + return connect(connectPlaywrightDriver.bind(connectPlaywrightDriver, options), child, '', handle, options.logger); } const env = { ...process.env }; @@ -199,8 +197,7 @@ export async function spawn(options: SpawnOptions): Promise { child = cp.spawn(electronPath, args, spawnOptions); instances.add(child); child.once('exit', () => instances.delete(child!)); - connectDriver = connectElectronDriver; - return connect(connectDriver, child, outPath, handle, options.logger); + return connect(connectElectronDriver, child, outPath, handle, options.logger); } async function copyExtension(extensionsPath: string, extId: string): Promise { diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index 9fd2c83b8e78a..da6a1aecec5f6 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -57,9 +57,15 @@ class PlaywrightDriver implements IDriver { try { await this._context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) }); } catch (error) { - console.warn(`Failed to stop playwright tracing.`); // do not fail the build when this fails + console.warn(`Failed to stop playwright tracing: ${error}`); } - await this._browser.close(); + + try { + await this._browser.close(); + } catch (error) { + console.warn(`Failed to close browser: ${error}`); + } + await teardown(); return false; @@ -207,9 +213,9 @@ export async function launch(userDataDir: string, _workspacePath: string, codeSe async function teardown(): Promise { if (server) { try { - await new Promise((c, e) => kill(server!.pid, err => err ? e(err) : c())); - } catch { - // noop + await new Promise((resolve, reject) => kill(server!.pid, err => err ? reject(err) : resolve())); + } catch (error) { + console.warn(`Error tearing down server: ${error}`); } server = undefined; From 438dc2f8b545f04238bcd0b15b0edd172d71d5a5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 18:10:35 +0100 Subject: [PATCH 0087/2210] #15756 show pre-release indicator if extension is pre-release version --- .../contrib/extensions/browser/extensionsWidgets.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 6746a8b97b4d2..203a737279071 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -176,7 +176,7 @@ export class PreReleaseIndicatorWidget extends ExtensionWidget { return; } - if (!this.extension.local?.isPreReleaseVersion) { + if (!this.extension.local?.isPreReleaseVersion && !this.extension.gallery?.properties.isPreReleaseVersion) { return; } @@ -482,9 +482,9 @@ export class ExtensionHoverWidget extends ExtensionWidget { const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); markdown.appendMarkdown(`**${this.extension.displayName}** _v${this.extension.version}_`); - if (this.extension.state === ExtensionState.Installed && this.extension.local?.isPreReleaseVersion) { + if (this.extension.local?.isPreReleaseVersion || this.extension.gallery?.properties.isPreReleaseVersion) { const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor); - markdown.appendMarkdown(` $(${preReleaseIcon.id})`); + markdown.appendMarkdown(`  ${localize('pre-release-label', "Pre-Release")} `); } markdown.appendText(`\n`); @@ -579,7 +579,7 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (!extension.hasPreReleaseVersion) { return undefined; } - if (extension.state === ExtensionState.Installed && extension.local?.isPreReleaseVersion) { + if (extension.local?.isPreReleaseVersion || extension.gallery?.properties.isPreReleaseVersion) { return undefined; } const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor); From fd83e2135cc22d69d6d47f064c3a984b79b74c5b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 20:02:54 +0100 Subject: [PATCH 0088/2210] #15756 show pre-release text in extension editor --- .../extensions/browser/extensionEditor.ts | 61 ++++++++++++++----- .../extensions/browser/extensionsWidgets.ts | 11 ++-- .../browser/media/extensionEditor.css | 18 ++++++ .../browser/media/extensionsWidgets.css | 2 +- 4 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 25ae36d54c6d5..6b8d484271237 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -23,7 +23,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, IExtension, ExtensionContainers, ExtensionEditorTab, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions'; -import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, PreReleaseIndicatorWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; +import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, PreReleaseIndicatorWidget, ExtensionHoverWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { @@ -68,7 +68,7 @@ import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { attachKeybindingLabelStyler } from 'vs/platform/theme/common/styler'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { errorIcon, infoIcon, starEmptyIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { errorIcon, infoIcon, preReleaseIcon, starEmptyIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; @@ -150,6 +150,7 @@ interface IExtensionEditorTemplate { actionsAndStatusContainer: HTMLElement; extensionActionBar: ActionBar; status: HTMLElement; + preReleaseText: HTMLElement; recommendation: HTMLElement; navbar: NavBar; content: HTMLElement; @@ -254,6 +255,7 @@ export class ExtensionEditor extends EditorPane { rating.setAttribute('role', 'link'); // #132645 const description = append(details, $('.description')); + const preReleaseText = append(details, $('.pre-release-text')); const actionsAndStatusContainer = append(details, $('.actions-status-container')); const extensionActionBar = this._register(new ActionBar(actionsAndStatusContainer, { @@ -303,6 +305,7 @@ export class ExtensionEditor extends EditorPane { rating, actionsAndStatusContainer, extensionActionBar, + preReleaseText, status, recommendation }; @@ -476,6 +479,7 @@ export class ExtensionEditor extends EditorPane { this.transientDisposables.add(disposable); } + this.setPreReleaseText(extension, template); this.setStatus(extension, extensionStatus, template); this.setRecommendationText(extension, template); @@ -524,6 +528,30 @@ export class ExtensionEditor extends EditorPane { this.editorLoadComplete = true; } + private setPreReleaseText(extension: IExtension, template: IExtensionEditorTemplate): void { + let preReleaseText: string | undefined; + reset(template.preReleaseText); + const disposables = this.transientDisposables.add(new DisposableStore()); + const updatePreReleaseText = () => { + const newPreReleaseText = ExtensionHoverWidget.getPreReleaseMessage(extension); + if (preReleaseText !== newPreReleaseText) { + preReleaseText = newPreReleaseText; + disposables.clear(); + reset(template.preReleaseText); + if (preReleaseText) { + append(template.preReleaseText, $(`span${ThemeIcon.asCSSSelector(preReleaseIcon)}`)); + disposables.add(this.renderMarkdownText(preReleaseText, template.preReleaseText)); + } + } + }; + updatePreReleaseText(); + this.transientDisposables.add(this.extensionsWorkbenchService.onChange(e => { + if (e && areSameExtensions(e.identifier, extension.identifier)) { + updatePreReleaseText(); + } + })); + } + private setStatus(extension: IExtension, extensionStatus: ExtensionStatusAction, template: IExtensionEditorTemplate): void { const disposables = new DisposableStore(); this.transientDisposables.add(disposables); @@ -536,16 +564,7 @@ export class ExtensionEditor extends EditorPane { const statusIconActionBar = disposables.add(new ActionBar(template.status, { animated: false })); statusIconActionBar.push(extensionStatus, { icon: true, label: false }); } - const rendered = disposables.add(renderMarkdown(new MarkdownString(status.message.value, { isTrusted: true, supportThemeIcons: true }), { - actionHandler: { - callback: (content) => { - this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError); - }, - disposables: disposables - } - })); - append(append(template.status, $('.status-text')), - rendered.element); + disposables.add(this.renderMarkdownText(status.message.value, append(template.status, $('.status-text')))); } }; updateStatus(); @@ -574,6 +593,20 @@ export class ExtensionEditor extends EditorPane { this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationText())); } + private renderMarkdownText(markdownText: string, parent: HTMLElement): IDisposable { + const disposables = new DisposableStore(); + const rendered = disposables.add(renderMarkdown(new MarkdownString(markdownText, { isTrusted: true, supportThemeIcons: true }), { + actionHandler: { + callback: (content) => { + this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError); + }, + disposables: disposables + } + })); + append(parent, rendered.element); + return disposables; + } + override clearInput(): void { this.contentDisposables.clear(); this.transientDisposables.clear(); @@ -1769,7 +1802,6 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = if (link) { collector.addRule(`.monaco-workbench .extension-editor .content .details .additional-details-container .resources-container a { color: ${link}; }`); collector.addRule(`.monaco-workbench .extension-editor .content .feature-contributions a { color: ${link}; }`); - collector.addRule(`.monaco-workbench .extension-editor > .header > .details > .actions-status-container > .status > .status-text a { color: ${link}; }`); } const activeLink = theme.getColor(textLinkActiveForeground); @@ -1778,9 +1810,6 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = .monaco-workbench .extension-editor .content .details .additional-details-container .resources-container a:active { color: ${activeLink}; }`); collector.addRule(`.monaco-workbench .extension-editor .content .feature-contributions a:hover, .monaco-workbench .extension-editor .content .feature-contributions a:active { color: ${activeLink}; }`); - collector.addRule(`.monaco-workbench .extension-editor > .header > .details > .actions-status-container > .status > .status-text a:hover, - .monaco-workbench .extension-editor > .header > .details > actions-status-container > .status > .status-text a:active { color: ${activeLink}; }`); - } const buttonHoverBackgroundColor = theme.getColor(buttonHoverBackground); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 203a737279071..a7251b84880aa 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -500,9 +500,10 @@ export class ExtensionHoverWidget extends ExtensionWidget { markdown.appendText(`\n`); } - const preReleaseMessage = this.getPreReleaseMessage(this.extension); + const preReleaseMessage = ExtensionHoverWidget.getPreReleaseMessage(this.extension); if (preReleaseMessage) { - markdown.appendMarkdown(preReleaseMessage); + const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor); + markdown.appendMarkdown(`$(${preReleaseIcon.id}) ${preReleaseMessage}`); markdown.appendText(`\n`); } @@ -575,17 +576,15 @@ export class ExtensionHoverWidget extends ExtensionWidget { return `$(${starEmptyIcon.id}) ${recommendation.reasonText}`; } - private getPreReleaseMessage(extension: IExtension): string | undefined { + static getPreReleaseMessage(extension: IExtension): string | undefined { if (!extension.hasPreReleaseVersion) { return undefined; } if (extension.local?.isPreReleaseVersion || extension.gallery?.properties.isPreReleaseVersion) { return undefined; } - const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor); const preReleaseVersionLink = `[${localize('Show prerelease version', "Pre-Release version")}](${URI.parse(`command:workbench.extensions.action.showPreReleaseVersion?${encodeURIComponent(JSON.stringify([extension.identifier.id]))}`)})`; - const message = localize('has prerelease', "This extension has a {0} available", preReleaseVersionLink); - return `$(${preReleaseIcon.id}) ${message}`; + return localize('has prerelease', "This extension has a {0} available", preReleaseVersionLink); } } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index af3cb435d0046..572237f365aaf 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -261,13 +261,31 @@ margin-top: 2px; } +.extension-editor > .header > .details > .pre-release-text p, .extension-editor > .header > .details > .actions-status-container > .status > .status-text p { margin-top: 0px; margin-bottom: 0px; } +.extension-editor > .header > .details > .pre-release-text a, +.extension-editor > .header > .details > .actions-status-container > .status > .status-text a { + color: var(--vscode-textLink-foreground) +} + +.extension-editor > .header > .details > .pre-release-text a:hover, .extension-editor > .header > .details > .actions-status-container > .status > .status-text a:hover { text-decoration: underline; + color: var(--vscode-textLink-activeForeground) +} + +.extension-editor > .header > .details > .pre-release-text a:active, +.extension-editor > .header > .details > .actions-status-container > .status > .status-text a:active { + color: var(--vscode-textLink-activeForeground) +} + +.extension-editor > .header > .details > .pre-release-text:not(:empty){ + margin-top: 10px; + display: flex; } .extension-editor > .header > .details > .recommendation { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css index dd487b3e73a38..3c58e5f40af62 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css @@ -60,6 +60,6 @@ } /* codicon colors */ -.codicon .codicon-extensions-pre-release { +.codicon.codicon-extensions-pre-release { color: var(--vscode-extensionIcon-preReleaseForeground); } From 7dfcd74e6340a90b2681b10b33948014f8912643 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 26 Nov 2021 20:09:01 +0100 Subject: [PATCH 0089/2210] #15756 adjust position of pre-release text --- .../contrib/extensions/browser/extensionEditor.ts | 2 +- .../extensions/browser/extensionsWidgets.ts | 14 +++++++------- .../extensions/browser/media/extensionEditor.css | 3 ++- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 6b8d484271237..5312c1636a708 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -255,7 +255,6 @@ export class ExtensionEditor extends EditorPane { rating.setAttribute('role', 'link'); // #132645 const description = append(details, $('.description')); - const preReleaseText = append(details, $('.pre-release-text')); const actionsAndStatusContainer = append(details, $('.actions-status-container')); const extensionActionBar = this._register(new ActionBar(actionsAndStatusContainer, { @@ -273,6 +272,7 @@ export class ExtensionEditor extends EditorPane { })); const status = append(actionsAndStatusContainer, $('.status')); + const preReleaseText = append(details, $('.pre-release-text')); const recommendation = append(details, $('.recommendation')); this._register(Event.chain(extensionActionBar.onDidRun) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index a7251b84880aa..f18ce4af2670c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -501,18 +501,12 @@ export class ExtensionHoverWidget extends ExtensionWidget { } const preReleaseMessage = ExtensionHoverWidget.getPreReleaseMessage(this.extension); - if (preReleaseMessage) { - const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor); - markdown.appendMarkdown(`$(${preReleaseIcon.id}) ${preReleaseMessage}`); - markdown.appendText(`\n`); - } - const extensionRuntimeStatus = this.extensionsWorkbenchService.getExtensionStatus(this.extension); const extensionStatus = this.extensionStatusAction.status; const reloadRequiredMessage = this.reloadAction.enabled ? this.reloadAction.tooltip : ''; const recommendationMessage = this.getRecommendationMessage(this.extension); - if (extensionRuntimeStatus || extensionStatus || reloadRequiredMessage || recommendationMessage) { + if (extensionRuntimeStatus || extensionStatus || reloadRequiredMessage || recommendationMessage || preReleaseMessage) { markdown.appendMarkdown(`---`); markdown.appendText(`\n`); @@ -555,6 +549,12 @@ export class ExtensionHoverWidget extends ExtensionWidget { markdown.appendText(`\n`); } + if (preReleaseMessage) { + const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor); + markdown.appendMarkdown(`$(${preReleaseIcon.id}) ${preReleaseMessage}`); + markdown.appendText(`\n`); + } + if (recommendationMessage) { markdown.appendMarkdown(recommendationMessage); markdown.appendText(`\n`); diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 572237f365aaf..b16594bab0942 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -284,8 +284,9 @@ } .extension-editor > .header > .details > .pre-release-text:not(:empty){ - margin-top: 10px; + margin-top: 5px; display: flex; + font-size: 90%; } .extension-editor > .header > .details > .recommendation { From 2eacc42cf3ec8975fc2d1c7089531c44e034a0fd Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 26 Nov 2021 22:37:14 -0500 Subject: [PATCH 0090/2210] Fix perf issue for InstallAnotherVersionAction --- .../common/extensionGalleryService.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6f15f76499f68..258f7b28c9da0 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -882,15 +882,22 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return []; } - const result: IGalleryExtensionVersion[] = []; - for (const version of galleryExtensions[0].versions) { - try { - if (result[result.length - 1]?.version !== version.version && await this.isRawExtensionVersionCompatible(version, includePreRelease, allTargetPlatforms, targetPlatform)) { - result.push({ version: version.version, date: version.lastUpdated, isPreReleaseVersion: isPreReleaseVersion(version) }); - } - } catch (error) { /* Ignore error and skip version */ } - } - return result; + const seenVersions = new Set(); + // Run checks in parallel to reduce wait time (fixes slow performance related to extensions with ownership changes) + const result = await Promise.all( + galleryExtensions[0].versions.map(async (version) => { + console.log('Testing version', version.version); + try { + if (!seenVersions.has(version.version) && await this.isRawExtensionVersionCompatible(version, includePreRelease, allTargetPlatforms, targetPlatform)) { + seenVersions.add(version.version); + return { version: version.version, date: version.lastUpdated, isPreReleaseVersion: isPreReleaseVersion(version) }; + } + } catch (error) { /* Ignore error and skip version */ } + return undefined; + }) + ); + //filter out skipped versions and return the rest + return result.filter(x => !!x) as IGalleryExtensionVersion[]; } private async getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise { From 7978c6da9b72bddbde24518150caf45fc1e9a9bf Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 28 Nov 2021 10:03:45 +0100 Subject: [PATCH 0091/2210] Improve "Run Test at Cursor" command Treat the text between two tests as belonging to the previous test; this improves the behavior for those test extensions that don't provide the full range of a test case, but only the start line (most do, it seems). Fixes #137984. --- .../testing/browser/testExplorerActions.ts | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index dbc7e0a847f68..c49e2511ded13 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -733,22 +733,47 @@ abstract class ExecuteTestAtCursor extends Action2 { const profileService = accessor.get(ITestProfileService); let bestNodes: InternalTestItem[] = []; + let bestNodesBefore: InternalTestItem[] = []; let bestRange: IRange | undefined; + let bestRangeBefore: IRange | undefined; // testsInFile will descend in the test tree. We assume that as we go // deeper, ranges get more specific. We'll want to run all tests whose // range is equal to the most specific range we find (see #133519) + // + // If we don't find any test whose range contains the position, we pick + // the closest one before the position. Again, if we find several tests + // whose range is equal to the closest one, we run them all. await showDiscoveringWhile(accessor.get(IProgressService), (async () => { for await (const test of testsInFile(testService.collection, model.uri)) { - if (!test.item.range || !Range.containsPosition(test.item.range, position) || !(profileService.capabilitiesForTest(test) & this.group)) { + if (!test.item.range || !(profileService.capabilitiesForTest(test) & this.group)) { continue; } - if (bestRange && Range.equalsRange(test.item.range, bestRange)) { - bestNodes.push(test); - } else { - bestRange = test.item.range; - bestNodes = [test]; + if (Range.containsPosition(test.item.range, position)) { + if (bestRange && Range.equalsRange(test.item.range, bestRange)) { + bestNodes.push(test); + } else { + bestRange = test.item.range; + bestNodes = [test]; + } + } + else if (test.item.range.startLineNumber < position.lineNumber + || (test.item.range.startLineNumber === position.lineNumber + && test.item.range.startColumn < position.column)) { + if (bestRangeBefore) { + if (Range.equalsRange(test.item.range, bestRangeBefore)) { + bestNodesBefore.push(test); + } else if (test.item.range.startLineNumber > bestRangeBefore.startLineNumber + || (test.item.range.startLineNumber === bestRangeBefore.startLineNumber + && test.item.range.startColumn > bestRangeBefore.startColumn)) { + bestRangeBefore = test.item.range; + bestNodesBefore = [test]; + } + } else { + bestRangeBefore = test.item.range; + bestNodesBefore = [test]; + } } } })()); @@ -760,6 +785,12 @@ abstract class ExecuteTestAtCursor extends Action2 { tests: bestNodes, }); } + else if (bestNodesBefore.length) { + await testService.runTests({ + group: this.group, + tests: bestNodesBefore, + }); + } } } From e71b28a444783d6b1cec4a6c68dc41a3a4d5d7ae Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 28 Nov 2021 10:30:52 +0100 Subject: [PATCH 0092/2210] Smoke test lifecycle changes (#137969) * rewrite teardown * :lipstick: * avoid spam errors * :lipstick: * split spawning browser from electron * await copyExtension * cleanup * refactor methods * cleanup * cleanup * cleanup * :up: deps --- test/automation/package.json | 6 +- test/automation/src/application.ts | 26 ++- test/automation/src/code.ts | 244 ++++-------------------- test/automation/src/electronDriver.ts | 200 +++++++++++++++++++ test/automation/src/extensions.ts | 12 ++ test/automation/src/playwrightDriver.ts | 211 +++++++++++--------- test/automation/yarn.lock | 34 ++-- test/integration/browser/src/index.ts | 11 +- test/smoke/package.json | 4 +- test/smoke/src/main.ts | 8 +- test/smoke/test/index.js | 2 +- test/smoke/yarn.lock | 12 +- 12 files changed, 424 insertions(+), 346 deletions(-) create mode 100644 test/automation/src/electronDriver.ts diff --git a/test/automation/package.json b/test/automation/package.json index 0f7a64daeb54b..34ffcf86386ef 100644 --- a/test/automation/package.json +++ b/test/automation/package.json @@ -22,15 +22,15 @@ "dependencies": { "mkdirp": "^1.0.4", "ncp": "^2.0.0", - "tmp": "0.1.0", + "tmp": "0.2.1", "tree-kill": "1.2.2", - "vscode-uri": "^2.0.3" + "vscode-uri": "3.0.2" }, "devDependencies": { "@types/mkdirp": "^1.0.1", "@types/ncp": "2.0.1", "@types/node": "14.x", - "@types/tmp": "0.1.0", + "@types/tmp": "0.2.2", "cpx2": "3.0.0", "npm-run-all": "^4.1.5", "typescript": "^4.3.2", diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 90fbff5a3afcd..d6e024dd54857 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -24,24 +24,19 @@ export interface ApplicationOptions extends SpawnOptions { export class Application { - private _code: Code | undefined; - private _workbench: Workbench | undefined; - constructor(private options: ApplicationOptions) { this._userDataPath = options.userDataDir; this._workspacePathOrFolder = options.workspacePath; } - get quality(): Quality { - return this.options.quality; - } + private _code: Code | undefined; + get code(): Code { return this._code!; } - get code(): Code { - return this._code!; - } + private _workbench: Workbench | undefined; + get workbench(): Workbench { return this._workbench!; } - get workbench(): Workbench { - return this._workbench!; + get quality(): Quality { + return this.options.quality; } get logger(): Logger { @@ -88,9 +83,11 @@ export class Application { async stop(): Promise { if (this._code) { - await this._code.exit(); - this._code.dispose(); - this._code = undefined; + try { + await this._code.exit(); + } finally { + this._code = undefined; + } } } @@ -102,6 +99,7 @@ export class Application { if (this.options.log) { this.logger.log('*** Screenshot recorded:', screenshotPath); } + fs.writeFileSync(screenshotPath, buffer); } } diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index a48ec0fee138d..a1c951d790b0e 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -4,90 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; -import * as cp from 'child_process'; import * as os from 'os'; -import * as fs from 'fs'; -import * as mkdirp from 'mkdirp'; -import { tmpName } from 'tmp'; -import { IDriver, connect as connectElectronDriver, IDisposable, IElement, Thenable, ILocalizedStrings, ILocaleInfo } from './driver'; -import { connect as connectPlaywrightDriver, launch } from './playwrightDriver'; +import * as cp from 'child_process'; +import { IDriver, IDisposable, IElement, Thenable, ILocalizedStrings, ILocaleInfo } from './driver'; +import { launch as launchElectron } from './electronDriver'; +import { launch as launchPlaywright } from './playwrightDriver'; import { Logger } from './logger'; -import { ncp } from 'ncp'; -import { URI } from 'vscode-uri'; +import { copyExtension } from './extensions'; const repoPath = path.join(__dirname, '../../..'); -function getDevElectronPath(): string { - const buildPath = path.join(repoPath, '.build'); - const product = require(path.join(repoPath, 'product.json')); - - switch (process.platform) { - case 'darwin': - return path.join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron'); - case 'linux': - return path.join(buildPath, 'electron', `${product.applicationName}`); - case 'win32': - return path.join(buildPath, 'electron', `${product.nameShort}.exe`); - default: - throw new Error('Unsupported platform.'); - } -} - -function getBuildElectronPath(root: string): string { - switch (process.platform) { - case 'darwin': - return path.join(root, 'Contents', 'MacOS', 'Electron'); - case 'linux': { - const product = require(path.join(root, 'resources', 'app', 'product.json')); - return path.join(root, product.applicationName); - } - case 'win32': { - const product = require(path.join(root, 'resources', 'app', 'product.json')); - return path.join(root, `${product.nameShort}.exe`); - } - default: - throw new Error('Unsupported platform.'); - } -} - -function getDevOutPath(): string { - return path.join(repoPath, 'out'); -} - -function getBuildOutPath(root: string): string { - switch (process.platform) { - case 'darwin': - return path.join(root, 'Contents', 'Resources', 'app', 'out'); - default: - return path.join(root, 'resources', 'app', 'out'); - } -} - -async function connect(connectDriver: typeof connectElectronDriver | typeof connectPlaywrightDriver, child: cp.ChildProcess | undefined, outPath: string, handlePath: string, logger: Logger): Promise { - let errCount = 0; - - while (true) { - try { - const { client, driver } = await connectDriver(outPath, handlePath); - return new Code(client, driver, logger, child?.pid); - } catch (err) { - if (++errCount > 50) { - if (child) { - child.kill(); - } - throw err; - } - - // retry - await new Promise(resolve => setTimeout(resolve, 100)); - } - } -} - -// Kill all running instances, when dead -const instances = new Set(); -process.once('exit', () => instances.forEach(code => code.kill())); - export interface SpawnOptions { codePath?: string; workspacePath: string; @@ -103,109 +29,29 @@ export interface SpawnOptions { browser?: 'chromium' | 'webkit' | 'firefox'; } -async function createDriverHandle(): Promise { - if ('win32' === os.platform()) { - const name = [...Array(15)].map(() => Math.random().toString(36)[3]).join(''); - return `\\\\.\\pipe\\${name}`; - } else { - return await new Promise((c, e) => tmpName((err, handlePath) => err ? e(err) : c(handlePath))); - } -} - export async function spawn(options: SpawnOptions): Promise { - const handle = await createDriverHandle(); - - let child: cp.ChildProcess | undefined; - copyExtension(options.extensionsPath, 'vscode-notebook-tests'); + await copyExtension(repoPath, options.extensionsPath, 'vscode-notebook-tests'); + // Browser smoke tests if (options.web) { - await launch(options.userDataDir, options.workspacePath, options.codePath, options.extensionsPath, Boolean(options.verbose)); - return connect(connectPlaywrightDriver.bind(connectPlaywrightDriver, options), child, '', handle, options.logger); - } - - const env = { ...process.env }; - const codePath = options.codePath; - const logsPath = path.join(repoPath, '.build', 'logs', options.remote ? 'smoke-tests-remote' : 'smoke-tests'); - const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); - - const args = [ - options.workspacePath, - '--skip-release-notes', - '--skip-welcome', - '--disable-telemetry', - '--no-cached-data', - '--disable-updates', - '--disable-keytar', - '--disable-crash-reporter', - '--disable-workspace-trust', - `--extensions-dir=${options.extensionsPath}`, - `--user-data-dir=${options.userDataDir}`, - `--logsPath=${logsPath}`, - '--driver', handle - ]; - - if (process.platform === 'linux') { - args.push('--disable-gpu'); // Linux has trouble in VMs to render properly with GPU enabled - } - - if (options.remote) { - // Replace workspace path with URI - args[0] = `--${options.workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(options.workspacePath).path}`; - - if (codePath) { - // running against a build: copy the test resolver extension - copyExtension(options.extensionsPath, 'vscode-test-resolver'); - } - args.push('--enable-proposed-api=vscode.vscode-test-resolver'); - const remoteDataDir = `${options.userDataDir}-server`; - mkdirp.sync(remoteDataDir); - - if (codePath) { - // running against a build: copy the test resolver extension into remote extensions dir - const remoteExtensionsDir = path.join(remoteDataDir, 'extensions'); - mkdirp.sync(remoteExtensionsDir); - copyExtension(remoteExtensionsDir, 'vscode-notebook-tests'); - } - - env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; - env['TESTRESOLVER_LOGS_FOLDER'] = path.join(logsPath, 'server'); - } - - const spawnOptions: cp.SpawnOptions = { env }; - - args.push('--enable-proposed-api=vscode.vscode-notebook-tests'); - - if (!codePath) { - args.unshift(repoPath); + return spawnBrowser(options); } - if (options.verbose) { - args.push('--driver-verbose'); - spawnOptions.stdio = ['ignore', 'inherit', 'inherit']; - } + // Electron smoke tests + return spawnElectron(options); +} - if (options.log) { - args.push('--log', options.log); - } +async function spawnBrowser(options: SpawnOptions): Promise { + const { serverProcess, client, driver } = await launchPlaywright(options.codePath, options.userDataDir, options.extensionsPath, options.workspacePath, Boolean(options.verbose), options); - if (options.extraArgs) { - args.push(...options.extraArgs); - } - - const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath(); - child = cp.spawn(electronPath, args, spawnOptions); - instances.add(child); - child.once('exit', () => instances.delete(child!)); - return connect(connectElectronDriver, child, outPath, handle, options.logger); + return new Code(client, driver, options.logger, serverProcess); } -async function copyExtension(extensionsPath: string, extId: string): Promise { - const dest = path.join(extensionsPath, extId); - if (!fs.existsSync(dest)) { - const orig = path.join(repoPath, 'extensions', extId); - await new Promise((c, e) => ncp(orig, dest, err => err ? e(err) : c())); - } +async function spawnElectron(options: SpawnOptions): Promise { + const { electronProcess, client, driver } = await launchElectron(options.codePath, options.userDataDir, options.extensionsPath, options.workspacePath, Boolean(options.verbose), Boolean(options.remote), options.log, options.extraArgs); + + return new Code(client, driver, options.logger, electronProcess); } async function poll( @@ -222,7 +68,7 @@ async function poll( if (trial > retryCount) { console.error('** Timeout!'); console.error(lastError); - console.error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`); + console.error(`*** Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`); throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`); } @@ -252,7 +98,7 @@ export class Code { private client: IDisposable, driver: IDriver, readonly logger: Logger, - private readonly pid: number | undefined + private readonly mainProcess: cp.ChildProcess ) { this.driver = new Proxy(driver, { get(target, prop, receiver) { @@ -292,49 +138,35 @@ export class Code { let done = false; // Start the exit flow via driver - const exitPromise = this.driver.exitApplication().then(veto => { + this.driver.exitApplication().then(veto => { if (veto) { done = true; reject(new Error('Smoke test exit call resulted in unexpected veto')); } }); - // If we know the `pid` of the smoke tested application - // use that as way to detect the exit of the application - const pid = this.pid; - if (typeof pid === 'number') { - (async () => { - let killCounter = 0; - while (!done) { - killCounter++; - - if (killCounter > 40) { - done = true; - reject(new Error('Smoke test exit call did not terminate main process after 20s, giving up')); - } - - try { - process.kill(pid, 0); // throws an exception if the main process doesn't exist anymore. - await new Promise(resolve => setTimeout(resolve, 500)); - } catch (error) { - done = true; - resolve(); - } + // Await the exit of the application + (async () => { + let retries = 0; + while (!done) { + retries++; + + if (retries > 40) { + done = true; + reject(new Error('Smoke test exit call did not terminate process after 20s, giving up')); } - })(); - } - // Otherwise await the exit promise (web). - else { - (async () => { try { - await exitPromise; - resolve(); + process.kill(this.mainProcess.pid!, 0); // throws an exception if the process doesn't exist anymore. + await new Promise(resolve => setTimeout(resolve, 500)); } catch (error) { - reject(new Error(`Smoke test exit call resulted in error: ${error}`)); + done = true; + resolve(); } - })(); - } + } + })(); + }).finally(() => { + this.dispose(); }); } diff --git a/test/automation/src/electronDriver.ts b/test/automation/src/electronDriver.ts new file mode 100644 index 0000000000000..6b879200576ec --- /dev/null +++ b/test/automation/src/electronDriver.ts @@ -0,0 +1,200 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as os from 'os'; +import { tmpName } from 'tmp'; +import { connect as connectElectronDriver, IDisposable, IDriver } from './driver'; +import { ChildProcess, spawn, SpawnOptions } from 'child_process'; +import * as mkdirp from 'mkdirp'; +import { promisify } from 'util'; +import * as kill from 'tree-kill'; +import { copyExtension } from './extensions'; +import { URI } from 'vscode-uri'; + +const repoPath = path.join(__dirname, '../../..'); + +export async function launch(codePath: string | undefined, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, remote: boolean, log: string | undefined, extraArgs: string[] | undefined): Promise<{ electronProcess: ChildProcess, client: IDisposable, driver: IDriver }> { + const env = { ...process.env }; + const logsPath = path.join(repoPath, '.build', 'logs', remote ? 'smoke-tests-remote' : 'smoke-tests'); + const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); + + const driverIPCHandle = await createDriverHandle(); + + const args = [ + workspacePath, + '--skip-release-notes', + '--skip-welcome', + '--disable-telemetry', + '--no-cached-data', + '--disable-updates', + '--disable-keytar', + '--disable-crash-reporter', + '--disable-workspace-trust', + `--extensions-dir=${extensionsPath}`, + `--user-data-dir=${userDataDir}`, + `--logsPath=${logsPath}`, + '--driver', driverIPCHandle + ]; + + if (process.platform === 'linux') { + args.push('--disable-gpu'); // Linux has trouble in VMs to render properly with GPU enabled + } + + if (remote) { + // Replace workspace path with URI + args[0] = `--${workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(workspacePath).path}`; + + if (codePath) { + // running against a build: copy the test resolver extension + await copyExtension(repoPath, extensionsPath, 'vscode-test-resolver'); + } + args.push('--enable-proposed-api=vscode.vscode-test-resolver'); + const remoteDataDir = `${userDataDir}-server`; + mkdirp.sync(remoteDataDir); + + if (codePath) { + // running against a build: copy the test resolver extension into remote extensions dir + const remoteExtensionsDir = path.join(remoteDataDir, 'extensions'); + mkdirp.sync(remoteExtensionsDir); + await copyExtension(repoPath, remoteExtensionsDir, 'vscode-notebook-tests'); + } + + env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; + env['TESTRESOLVER_LOGS_FOLDER'] = path.join(logsPath, 'server'); + } + + const spawnOptions: SpawnOptions = { env }; + + args.push('--enable-proposed-api=vscode.vscode-notebook-tests'); + + if (!codePath) { + args.unshift(repoPath); + } + + if (verbose) { + args.push('--driver-verbose'); + spawnOptions.stdio = ['ignore', 'inherit', 'inherit']; + } + + if (log) { + args.push('--log', log); + } + + if (extraArgs) { + args.push(...extraArgs); + } + + const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath(); + const electronProcess = spawn(electronPath, args, spawnOptions); + + if (verbose) { + console.info(`*** Started electron for desktop smoke tests on pid ${electronProcess.pid}`); + } + + let electronProcessDidExit = false; + electronProcess.once('exit', (code, signal) => { + if (verbose) { + console.info(`*** Electron for desktop smoke tests terminated (pid: ${electronProcess.pid}, code: ${code}, signal: ${signal})`); + } + electronProcessDidExit = true; + }); + + process.once('exit', () => { + if (!electronProcessDidExit) { + electronProcess.kill(); + } + }); + + let retries = 0; + + while (true) { + try { + const { client, driver } = await connectElectronDriver(outPath, driverIPCHandle); + return { electronProcess, client, driver }; + } catch (err) { + + // give up + if (++retries > 30) { + console.error(`*** Error connecting driver: ${err}. Giving up...`); + + try { + await promisify(kill)(electronProcess.pid!); + } catch (error) { + console.warn(`*** Error tearing down: ${error}`); + } + + throw err; + } + + // retry + else { + if ((err as NodeJS.ErrnoException).code !== 'ENOENT' /* ENOENT is expected for as long as the server has not started on the socket */) { + console.error(`*** Error connecting driver: ${err}. Attempting to retry...`); + } + + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + } +} + +function getDevElectronPath(): string { + const buildPath = path.join(repoPath, '.build'); + const product = require(path.join(repoPath, 'product.json')); + + switch (process.platform) { + case 'darwin': + return path.join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron'); + case 'linux': + return path.join(buildPath, 'electron', `${product.applicationName}`); + case 'win32': + return path.join(buildPath, 'electron', `${product.nameShort}.exe`); + default: + throw new Error('Unsupported platform.'); + } +} + +function getBuildElectronPath(root: string): string { + switch (process.platform) { + case 'darwin': + return path.join(root, 'Contents', 'MacOS', 'Electron'); + case 'linux': { + const product = require(path.join(root, 'resources', 'app', 'product.json')); + return path.join(root, product.applicationName); + } + case 'win32': { + const product = require(path.join(root, 'resources', 'app', 'product.json')); + return path.join(root, `${product.nameShort}.exe`); + } + default: + throw new Error('Unsupported platform.'); + } +} + +function getDevOutPath(): string { + return path.join(repoPath, 'out'); +} + +function getBuildOutPath(root: string): string { + switch (process.platform) { + case 'darwin': + return path.join(root, 'Contents', 'Resources', 'app', 'out'); + default: + return path.join(root, 'resources', 'app', 'out'); + } +} + +async function createDriverHandle(): Promise { + + // Windows + if ('win32' === os.platform()) { + const name = [...Array(15)].map(() => Math.random().toString(36)[3]).join(''); + return `\\\\.\\pipe\\${name}`; + } + + // Posix + return promisify(tmpName)(); +} diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index 0288d79374fbf..b0c56f948ea0a 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -5,6 +5,10 @@ import { Viewlet } from './viewlet'; import { Code } from './code'; +import path = require('path'); +import fs = require('fs'); +import { ncp } from 'ncp'; +import { promisify } from 'util'; const SEARCH_BOX = 'div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor textarea'; @@ -49,5 +53,13 @@ export class Extensions extends Viewlet { await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled) .extension-action[title="Disable this extension"]`); } } +} + +export async function copyExtension(repoPath: string, extensionsPath: string, extId: string): Promise { + const dest = path.join(extensionsPath, extId); + if (!fs.existsSync(dest)) { + const orig = path.join(repoPath, 'extensions', extId); + return promisify(ncp)(orig, dest); + } } diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index da6a1aecec5f6..d5373bd64f303 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -35,51 +35,58 @@ const vscodeToPlaywrightKey: { [key: string]: string } = { let traceCounter = 1; -function buildDriver(browser: playwright.Browser, context: playwright.BrowserContext, page: playwright.Page): IDriver { - return new PlaywrightDriver(browser, context, page); -} - class PlaywrightDriver implements IDriver { + _serviceBrand: undefined; - page: playwright.Page; + constructor( - private readonly _browser: playwright.Browser, - private readonly _context: playwright.BrowserContext, - private readonly _page: playwright.Page + private readonly server: ChildProcess, + private readonly browser: playwright.Browser, + private readonly context: playwright.BrowserContext, + readonly page: playwright.Page ) { - this.page = _page; } - async getWindowIds() { return [1]; } - async capturePage() { return ''; } - async reloadWindow(windowId: number) { } + async getWindowIds() { + return [1]; + } + + async capturePage() { + return ''; + } + + async reloadWindow(windowId: number) { + throw new Error('Unsupported'); + } + async exitApplication() { try { - await this._context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) }); + await this.context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) }); } catch (error) { console.warn(`Failed to stop playwright tracing: ${error}`); } try { - await this._browser.close(); + await this.browser.close(); } catch (error) { console.warn(`Failed to close browser: ${error}`); } - await teardown(); + await teardown(this.server); return false; } + async dispatchKeybinding(windowId: number, keybinding: string) { const chords = keybinding.split(' '); for (let i = 0; i < chords.length; i++) { const chord = chords[i]; if (i > 0) { - await timeout(100); + await this.timeout(100); } if (keybinding.startsWith('Alt') || keybinding.startsWith('Control') || keybinding.startsWith('Backspace')) { - await this._page.keyboard.press(keybinding); + await this.page.keyboard.press(keybinding); return; } @@ -89,80 +96,108 @@ class PlaywrightDriver implements IDriver { if (keys[i] in vscodeToPlaywrightKey) { keys[i] = vscodeToPlaywrightKey[keys[i]]; } - await this._page.keyboard.down(keys[i]); + await this.page.keyboard.down(keys[i]); keysDown.push(keys[i]); } while (keysDown.length > 0) { - await this._page.keyboard.up(keysDown.pop()!); + await this.page.keyboard.up(keysDown.pop()!); } } - await timeout(100); + await this.timeout(100); } + async click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined) { const { x, y } = await this.getElementXY(windowId, selector, xoffset, yoffset); - await this._page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0)); + await this.page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0)); } + async doubleClick(windowId: number, selector: string) { await this.click(windowId, selector, 0, 0); - await timeout(60); + await this.timeout(60); await this.click(windowId, selector, 0, 0); - await timeout(100); + await this.timeout(100); } async setValue(windowId: number, selector: string, text: string) { - return this._page.evaluate(([driver, selector, text]) => driver.setValue(selector, text), [await this._getDriverHandle(), selector, text] as const); + return this.page.evaluate(([driver, selector, text]) => driver.setValue(selector, text), [await this._getDriverHandle(), selector, text] as const); } + async getTitle(windowId: number) { return this._evaluateWithDriver(([driver]) => driver.getTitle()); } + async isActiveElement(windowId: number, selector: string) { - return this._page.evaluate(([driver, selector]) => driver.isActiveElement(selector), [await this._getDriverHandle(), selector] as const); + return this.page.evaluate(([driver, selector]) => driver.isActiveElement(selector), [await this._getDriverHandle(), selector] as const); } + async getElements(windowId: number, selector: string, recursive: boolean = false) { - return this._page.evaluate(([driver, selector, recursive]) => driver.getElements(selector, recursive), [await this._getDriverHandle(), selector, recursive] as const); + return this.page.evaluate(([driver, selector, recursive]) => driver.getElements(selector, recursive), [await this._getDriverHandle(), selector, recursive] as const); } + async getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number) { - return this._page.evaluate(([driver, selector, xoffset, yoffset]) => driver.getElementXY(selector, xoffset, yoffset), [await this._getDriverHandle(), selector, xoffset, yoffset] as const); + return this.page.evaluate(([driver, selector, xoffset, yoffset]) => driver.getElementXY(selector, xoffset, yoffset), [await this._getDriverHandle(), selector, xoffset, yoffset] as const); } + async typeInEditor(windowId: number, selector: string, text: string) { - return this._page.evaluate(([driver, selector, text]) => driver.typeInEditor(selector, text), [await this._getDriverHandle(), selector, text] as const); + return this.page.evaluate(([driver, selector, text]) => driver.typeInEditor(selector, text), [await this._getDriverHandle(), selector, text] as const); } + async getTerminalBuffer(windowId: number, selector: string) { - return this._page.evaluate(([driver, selector]) => driver.getTerminalBuffer(selector), [await this._getDriverHandle(), selector] as const); + return this.page.evaluate(([driver, selector]) => driver.getTerminalBuffer(selector), [await this._getDriverHandle(), selector] as const); } + async writeInTerminal(windowId: number, selector: string, text: string) { - return this._page.evaluate(([driver, selector, text]) => driver.writeInTerminal(selector, text), [await this._getDriverHandle(), selector, text] as const); + return this.page.evaluate(([driver, selector, text]) => driver.writeInTerminal(selector, text), [await this._getDriverHandle(), selector, text] as const); } + async getLocaleInfo(windowId: number) { return this._evaluateWithDriver(([driver]) => driver.getLocaleInfo()); } + async getLocalizedStrings(windowId: number) { return this._evaluateWithDriver(([driver]) => driver.getLocalizedStrings()); } private async _evaluateWithDriver(pageFunction: PageFunction[], T>) { - return this._page.evaluate(pageFunction, [await this._getDriverHandle()]); + return this.page.evaluate(pageFunction, [await this._getDriverHandle()]); + } + + private timeout(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); } // TODO: Cache private async _getDriverHandle(): Promise> { - return this._page.evaluateHandle('window.driver'); + return this.page.evaluateHandle('window.driver'); } } -function timeout(ms: number): Promise { - return new Promise(r => setTimeout(r, ms)); +let port = 9000; + +export interface PlaywrightOptions { + readonly browser?: 'chromium' | 'webkit' | 'firefox'; + readonly headless?: boolean; } -let port = 9000; -let server: ChildProcess | undefined; -let endpoint: string | undefined; -let workspacePath: string | undefined; +export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, options: PlaywrightOptions = {}): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver }> { + + // Launch server + const { serverProcess, endpoint } = await launchServer(userDataDir, codeServerPath, extensionsPath, verbose); -export async function launch(userDataDir: string, _workspacePath: string, codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, extPath: string, verbose: boolean): Promise { - workspacePath = _workspacePath; + // Launch browser + const { browser, context, page } = await launchBrowser(options, endpoint, workspacePath); + return { + serverProcess, + client: { + dispose: () => { /* there is no client to dispose for browser, teardown is triggered via exitApplication call */ } + }, + driver: new PlaywrightDriver(serverProcess, browser, context, page) + }; +} + +async function launchServer(userDataDir: string, codeServerPath: string | undefined, extensionsPath: string, verbose: boolean) { const agentFolder = userDataDir; await promisify(mkdir)(agentFolder); const env = { @@ -171,7 +206,7 @@ export async function launch(userDataDir: string, _workspacePath: string, codeSe ...process.env }; - const args = ['--disable-telemetry', '--port', `${port++}`, '--browser', 'none', '--driver', 'web', '--extensions-dir', extPath]; + const args = ['--disable-telemetry', '--port', `${port++}`, '--browser', 'none', '--driver', 'web', '--extensions-dir', extensionsPath]; let serverLocation: string | undefined; if (codeServerPath) { @@ -192,79 +227,83 @@ export async function launch(userDataDir: string, _workspacePath: string, codeSe } } - server = spawn( + const serverProcess = spawn( serverLocation, args, { env } ); if (verbose) { - server.stderr?.on('data', error => console.log(`Server stderr: ${error}`)); - server.stdout?.on('data', data => console.log(`Server stdout: ${data}`)); - } - - process.on('exit', teardown); - process.on('SIGINT', teardown); - process.on('SIGTERM', teardown); - - endpoint = await waitForEndpoint(); -} + console.info(`*** Started server for browser smoke tests (pid: ${serverProcess.pid})`); + serverProcess.once('exit', (code, signal) => console.info(`*** Server for browser smoke tests terminated (pid: ${serverProcess.pid}, code: ${code}, signal: ${signal})`)); -async function teardown(): Promise { - if (server) { - try { - await new Promise((resolve, reject) => kill(server!.pid, err => err ? reject(err) : resolve())); - } catch (error) { - console.warn(`Error tearing down server: ${error}`); - } - - server = undefined; + serverProcess.stderr?.on('data', error => console.log(`Server stderr: ${error}`)); + serverProcess.stdout?.on('data', data => console.log(`Server stdout: ${data}`)); } -} -function waitForEndpoint(): Promise { - return new Promise(r => { - server!.stdout?.on('data', (d: Buffer) => { - const matches = d.toString('ascii').match(/Web UI available at (.+)/); - if (matches !== null) { - r(matches[1]); - } - }); - }); -} + process.on('exit', () => teardown(serverProcess)); + process.on('SIGINT', () => teardown(serverProcess)); + process.on('SIGTERM', () => teardown(serverProcess)); -interface Options { - readonly browser?: 'chromium' | 'webkit' | 'firefox'; - readonly headless?: boolean; + return { + serverProcess, + endpoint: await waitForEndpoint(serverProcess) + }; } -export async function connect(options: Options = {}): Promise<{ client: IDisposable, driver: IDriver }> { +async function launchBrowser(options: PlaywrightOptions, endpoint: string, workspacePath: string) { const browser = await playwright[options.browser ?? 'chromium'].launch({ headless: options.headless ?? false }); const context = await browser.newContext(); + try { await context.tracing.start({ screenshots: true, snapshots: true }); } catch (error) { console.warn(`Failed to start playwright tracing.`); // do not fail the build when this fails } + const page = await context.newPage(); await page.setViewportSize({ width, height }); - page.on('pageerror', async error => console.error(`Playwright ERROR: page error: ${error}`)); + + page.on('pageerror', async (error) => console.error(`Playwright ERROR: page error: ${error}`)); page.on('crash', page => console.error('Playwright ERROR: page crash')); - page.on('response', async response => { + page.on('response', async (response) => { if (response.status() >= 400) { console.error(`Playwright ERROR: HTTP status ${response.status()} for ${response.url()}`); } }); + const payloadParam = `[["enableProposedApi",""],["skipWelcome","true"]]`; await page.goto(`${endpoint}&folder=vscode-remote://localhost:9888${URI.file(workspacePath!).path}&payload=${payloadParam}`); - return { - client: { - dispose: () => { - browser.close(); - teardown(); + return { browser, context, page }; +} + +async function teardown(server: ChildProcess): Promise { + let retries = 0; + while (retries < 3) { + retries++; + + try { + if (typeof server.pid === 'number') { + await promisify(kill)(server.pid); } - }, - driver: buildDriver(browser, context, page) - }; + + return; + } catch (error) { + console.warn(`Error tearing down server: ${error} (attempt: ${retries})`); + } + } + + console.error(`Gave up tearing down server after ${retries} attempts...`); +} + +function waitForEndpoint(server: ChildProcess): Promise { + return new Promise(resolve => { + server.stdout?.on('data', (d: Buffer) => { + const matches = d.toString('ascii').match(/Web UI available at (.+)/); + if (matches !== null) { + resolve(matches[1]); + } + }); + }); } diff --git a/test/automation/yarn.lock b/test/automation/yarn.lock index c4d5ceeb0d84b..d09c2d64ebb7e 100644 --- a/test/automation/yarn.lock +++ b/test/automation/yarn.lock @@ -26,10 +26,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.43.tgz#26bcbb0595b305400e8ceaf9a127a7f905ae49c8" integrity sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ== -"@types/tmp@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.1.0.tgz#19cf73a7bcf641965485119726397a096f0049bd" - integrity sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA== +"@types/tmp@0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.2.tgz#424537a3b91828cb26aaf697f21ae3cd1b69f7e7" + integrity sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A== ansi-styles@^3.2.1: version "3.2.1" @@ -545,10 +545,10 @@ resolve@^1.12.0: is-core-module "^2.2.0" path-parse "^1.0.6" -rimraf@^2.6.3: - version "2.7.0" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.0.tgz#eb43198c5e2fb83b9323abee63bd87836f9a7c85" - integrity sha512-4Liqw7ccABzsWV5BzeZeGRSq7KWIgQYzOcmRDEwSX4WAawlQpcAFXZ1Kid72XYrjSnK5yxOS6Gez/iGusYE/Pw== +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" @@ -654,12 +654,12 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -tmp@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" - integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== +tmp@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== dependencies: - rimraf "^2.6.3" + rimraf "^3.0.0" tree-kill@1.2.2: version "1.2.2" @@ -684,10 +684,10 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -vscode-uri@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.3.tgz#25e5f37f552fbee3cec7e5f80cef8469cefc6543" - integrity sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw== +vscode-uri@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.2.tgz#ecfd1d066cb8ef4c3a208decdbab9a8c23d055d0" + integrity sha512-jkjy6pjU1fxUvI51P+gCsxg1u2n8LSt0W6KrCNQceaziKzff74GoWmjVG46KieVzybO1sttPQmYfrwSHey7GUA== watch@^1.0.2: version "1.0.2" diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 063d1927d26e2..ae25547bce1ce 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -12,6 +12,7 @@ import * as rimraf from 'rimraf'; import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; import * as optimistLib from 'optimist'; +import { promisify } from 'util'; const optimist = optimistLib .describe('workspacePath', 'path to the workspace (folder or *.code-workspace file) to open in the test').string('workspacePath') @@ -84,7 +85,7 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith } try { - await pkill(server.pid); + await promisify(kill)(server.pid!); } catch (error) { console.error(`Error when killing server process tree: ${error}`); } @@ -93,7 +94,7 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith }); } -function consoleLogFn(msg) { +function consoleLogFn(msg: playwright.ConsoleMessage) { const type = msg.type(); const candidate = console[type]; if (candidate) { @@ -107,12 +108,6 @@ function consoleLogFn(msg) { return console.log; } -function pkill(pid: number): Promise { - return new Promise((c, e) => { - kill(pid, error => error ? e(error) : c()); - }); -} - async function launchServer(browserType: BrowserType): Promise<{ endpoint: url.UrlWithStringQuery, server: cp.ChildProcess }> { // Ensure a tmp user-data-dir is used for the tests diff --git a/test/smoke/package.json b/test/smoke/package.json index 3fb62eab3f993..60292ae8c6ce8 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -14,7 +14,7 @@ "mkdirp": "^1.0.4", "ncp": "^2.0.0", "node-fetch": "^2.6.1", - "rimraf": "^2.6.1", + "rimraf": "3.0.2", "vscode-test": "^1.6.1" }, "devDependencies": { @@ -23,7 +23,7 @@ "@types/ncp": "2.0.1", "@types/node": "14.x", "@types/node-fetch": "^2.5.10", - "@types/rimraf": "^2.0.4", + "@types/rimraf": "3.0.2", "npm-run-all": "^4.1.5", "typescript": "^4.3.2", "watch": "^1.0.2" diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 05455c15a7830..48aa7267e644b 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; +import { timeout } from './utils'; +import { promisify } from 'util'; import { gracefulify } from 'graceful-fs'; import * as cp from 'child_process'; import * as path from 'path'; @@ -334,15 +336,15 @@ before(async function () { }); after(async function () { - await new Promise(c => setTimeout(c, 500)); // wait for shutdown + await timeout(500); // wait for shutdown if (opts.log) { const logsDir = path.join(userDataDir, 'logs'); const destLogsDir = path.join(path.dirname(opts.log), 'logs'); - await new Promise((c, e) => ncp(logsDir, destLogsDir, err => err ? e(err) : c(undefined))); + await promisify(ncp)(logsDir, destLogsDir); } - await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c(undefined))); + await new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 50 }, error => error ? reject(error) : resolve())); }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { diff --git a/test/smoke/test/index.js b/test/smoke/test/index.js index a2d16f5e3061b..6d07772193379 100644 --- a/test/smoke/test/index.js +++ b/test/smoke/test/index.js @@ -13,7 +13,7 @@ const opts = minimist(args, { string: ['f', 'g'] }); -const suite = opts['web'] ? 'Browser Smoke Tests' : 'Smoke Tests'; +const suite = opts['web'] ? 'Browser Smoke Tests' : 'Desktop Smoke Tests'; const options = { color: true, diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index b5b30bcd9c0f4..28bb27266551d 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -63,10 +63,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.43.tgz#26bcbb0595b305400e8ceaf9a127a7f905ae49c8" integrity sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ== -"@types/rimraf@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.4.tgz#403887b0b53c6100a6c35d2ab24f6ccc042fec46" - integrity sha512-8gBudvllD2A/c0CcEX/BivIDorHFt5UI5m46TsNj8DjWCCTTZT74kEe4g+QsY7P/B9WdO98d82zZgXO/RQzu2Q== +"@types/rimraf@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.2.tgz#a63d175b331748e5220ad48c901d7bbf1f44eef8" + integrity sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ== dependencies: "@types/glob" "*" "@types/node" "*" @@ -648,14 +648,14 @@ resolve@^1.10.0: is-core-module "^2.1.0" path-parse "^1.0.6" -rimraf@2, rimraf@^2.6.1: +rimraf@2: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" -rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== From c9afb8d42e022e0ed7497cdb244f1db06b9d0c97 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 28 Nov 2021 12:10:53 +0100 Subject: [PATCH 0093/2210] tests - exclude more for node.js (#137853) --- .../contrib/testing/test/common/testResultService.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index 925909fe0f45b..efecaa151040c 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -25,7 +25,7 @@ export const emptyOutputController = () => new LiveOutputController( () => Promise.resolve(bufferToStream(VSBuffer.alloc(0))), ); -suite('Workbench - Test Results Service', () => { +((isNative && !isElectron) ? test.skip /* TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 */ : test)('Workbench - Test Results Service', () => { const getLabelsIn = (it: Iterable) => [...it].map(t => t.item.label).sort(); const getChangeSummary = () => [...changed] .map(c => ({ reason: c.reason, label: c.item.item.label })) @@ -312,7 +312,7 @@ suite('Workbench - Test Results Service', () => { }); }); - ((isNative && !isElectron) ? test.skip /* TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 */ : test)('resultItemParents', function () { + test('resultItemParents', function () { assert.deepStrictEqual([...resultItemParents(r, r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString())!)], [ r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString()), r.getStateById(new TestId(['ctrlId', 'id-a']).toString()), From 6fb0fdd80fc0c552c79c448e000262950b727eb2 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sun, 28 Nov 2021 11:52:23 -0800 Subject: [PATCH 0094/2210] fix coloring of layout control icon --- .../workbench/browser/parts/titlebar/media/titlebarpart.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 5daf455db32c8..c0759bfe6aaa5 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -160,6 +160,10 @@ display: inline-block; } +.monaco-workbench .part.titlebar > .window-controls-container.show-layout-control > .layout-dropdown-container .codicon { + color: inherit; +} + .monaco-workbench .part.titlebar > .window-controls-container > .window-icon { display: inline-block; line-height: 30px; From f91602a1902fa92493ca0b87dd33c9de8b73c71b Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sun, 28 Nov 2021 12:24:33 -0800 Subject: [PATCH 0095/2210] fixes #136851 --- src/vs/base/browser/ui/actionbar/actionbar.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 6e7258d645816..a3714bb05362f 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -425,16 +425,16 @@ export class ActionBar extends Disposable implements IActionRunner { } private focusFirst(): boolean { - this.focusedItem = this.length() > 1 ? 1 : 0; - return this.focusPrevious(); + this.focusedItem = this.length() - 1; + return this.focusNext(true); } private focusLast(): boolean { - this.focusedItem = this.length() < 2 ? 0 : this.length() - 2; - return this.focusNext(); + this.focusedItem = 0; + return this.focusPrevious(true); } - protected focusNext(): boolean { + protected focusNext(forceLoop?: boolean): boolean { if (typeof this.focusedItem === 'undefined') { this.focusedItem = this.viewItems.length - 1; } else if (this.viewItems.length <= 1) { @@ -445,7 +445,7 @@ export class ActionBar extends Disposable implements IActionRunner { let item: IActionViewItem; do { - if (this.options.preventLoopNavigation && this.focusedItem + 1 >= this.viewItems.length) { + if (!forceLoop && this.options.preventLoopNavigation && this.focusedItem + 1 >= this.viewItems.length) { this.focusedItem = startIndex; return false; } @@ -458,7 +458,7 @@ export class ActionBar extends Disposable implements IActionRunner { return true; } - protected focusPrevious(): boolean { + protected focusPrevious(forceLoop?: boolean): boolean { if (typeof this.focusedItem === 'undefined') { this.focusedItem = 0; } else if (this.viewItems.length <= 1) { @@ -471,7 +471,7 @@ export class ActionBar extends Disposable implements IActionRunner { do { this.focusedItem = this.focusedItem - 1; if (this.focusedItem < 0) { - if (this.options.preventLoopNavigation) { + if (!forceLoop && this.options.preventLoopNavigation) { this.focusedItem = startIndex; return false; } From 8767fc8e47b17ded235acbf0535b1d5f3e2b48e3 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sun, 28 Nov 2021 14:19:51 -0800 Subject: [PATCH 0096/2210] fixes #136803 --- src/vs/workbench/browser/parts/views/treeView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index fc647e3e4bcc5..2655c4a7e975f 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -534,7 +534,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { multipleSelectionSupport: this.canSelectMany, dnd: this.treeViewDnd, overrideStyles: { - listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND + listBackground: this.viewLocation === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND } }) as WorkbenchAsyncDataTree); treeMenus.setContextKeyService(this.tree.contextKeyService); From 725ed948b35c05b786bb7e6e3ebfc3583b1939b0 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sun, 28 Nov 2021 15:01:09 -0800 Subject: [PATCH 0097/2210] add padding for layout control --- src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index c0759bfe6aaa5..042cd52fe802a 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -134,7 +134,7 @@ .monaco-workbench.mac:not(web) .part.titlebar > .window-controls-container { position: absolute; - right: 0px; + right: 8px; width: 28px; display: none; } From d1e3c7ba6d1d4299def5bb4021d7537c95f2a0aa Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sun, 28 Nov 2021 15:28:49 -0800 Subject: [PATCH 0098/2210] tweak layout control menu entries --- .../browser/actions/layoutActions.ts | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 17a1962f940ed..73d44f81927a7 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -99,11 +99,15 @@ registerAction2(class extends Action2 { category: CATEGORIES.View, f1: true, toggled: IsCenteredLayoutContext, - menu: { + menu: [{ id: MenuId.MenubarAppearanceMenu, group: '1_toggle_view', order: 3 - } + }, { + id: MenuId.LayoutControlMenu, + group: '1_toggle_view', + order: 3 + }] }); } @@ -205,6 +209,16 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 2 }); +MenuRegistry.appendMenuItem(MenuId.LayoutControlMenu, { + group: '3_workbench_layout_move', + command: { + id: ToggleSidebarPositionAction.ID, + title: localize({ key: 'miMoveSidebarRightNoMnemonic', comment: ['&& denotes a mnemonic'] }, "Move Side Bar Right") + }, + when: ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), + order: 2 +}); + MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '3_workbench_layout_move', command: { @@ -215,6 +229,16 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 2 }); +MenuRegistry.appendMenuItem(MenuId.LayoutControlMenu, { + group: '3_workbench_layout_move', + command: { + id: ToggleSidebarPositionAction.ID, + title: localize({ key: 'miMoveSidebarLeftNoMnemonic', comment: ['&& denotes a mnemonic'] }, "Move Side Bar Left") + }, + when: ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), + order: 2 +}); + // --- Toggle Editor Visibility registerAction2(class extends Action2 { @@ -234,11 +258,7 @@ registerAction2(class extends Action2 { id: MenuId.MenubarAppearanceMenu, group: '2_workbench_layout', order: 5 - }, { - id: MenuId.LayoutControlMenu, - group: '0_workbench_layout', - order: 5 - }] + }] }); } From 273bdea9e7f8806df669ebb9bdb58c9ef00f14fa Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sun, 28 Nov 2021 16:08:37 -0800 Subject: [PATCH 0099/2210] fixes #136805 --- src/vs/workbench/browser/parts/panel/panelPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index d6d37e947c85b..d9c9b83f49d65 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -398,7 +398,7 @@ export abstract class BasePanelPart extends CompositePart impleme const viewContainer = this.getViewContainer(panelDescriptor.id); if (viewContainer?.hideIfEmpty) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - if (viewContainerModel.activeViewDescriptors.length === 0 && this.compositeBar.getPinnedComposites().length > 1) { + if (viewContainerModel.activeViewDescriptors.length === 0) { this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding } } From 368154bd0b42e3b198decd6e09144cfb30d431e7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 08:04:30 +0100 Subject: [PATCH 0100/2210] smoke - print task list when deleting workspace path fails --- test/smoke/package.json | 1 + test/smoke/src/main.ts | 13 ++++++++++- test/smoke/yarn.lock | 50 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/test/smoke/package.json b/test/smoke/package.json index 60292ae8c6ce8..f6ba2fd84334c 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -15,6 +15,7 @@ "ncp": "^2.0.0", "node-fetch": "^2.6.1", "rimraf": "3.0.2", + "tasklist": "^5.0.0", "vscode-test": "^1.6.1" }, "devDependencies": { diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 48aa7267e644b..2b41e19510c2f 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -17,6 +17,7 @@ import { ncp } from 'ncp'; import * as vscodetest from 'vscode-test'; import fetch from 'node-fetch'; import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger } from '../../automation'; +import { tasklist } from 'tasklist'; import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; import { setup as setupPreferencesTests } from './areas/preferences/preferences.test'; @@ -344,7 +345,17 @@ after(async function () { await promisify(ncp)(logsDir, destLogsDir); } - await new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 50 }, error => error ? reject(error) : resolve())); + try { + await new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 10 }, error => error ? reject(error) : resolve())); + } catch (error) { + console.error(`Unable to delete smoke test workspace: ${error}`); + + if (process.platform === 'win32' && error.code === 'EPERM') { + console.log(await tasklist()); + } + + throw error; + } }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 28bb27266551d..15522154f4447 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -195,6 +195,31 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +csv-generate@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-3.4.3.tgz#bc42d943b45aea52afa896874291da4b9108ffff" + integrity sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw== + +csv-parse@^4.16.3: + version "4.16.3" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.16.3.tgz#7ca624d517212ebc520a36873c3478fa66efbaf7" + integrity sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg== + +csv-stringify@^5.6.5: + version "5.6.5" + resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-5.6.5.tgz#c6d74badda4b49a79bf4e72f91cce1e33b94de00" + integrity sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A== + +csv@^5.5.0: + version "5.5.3" + resolved "https://registry.yarnpkg.com/csv/-/csv-5.5.3.tgz#cd26c1e45eae00ce6a9b7b27dcb94955ec95207d" + integrity sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g== + dependencies: + csv-generate "^3.4.3" + csv-parse "^4.16.3" + csv-stringify "^5.6.5" + stream-transform "^2.1.3" + debug@4: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" @@ -489,6 +514,11 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +mixme@^0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.4.tgz#8cb3bd0cd32a513c161bf1ca99d143f0bcf2eff3" + integrity sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw== + "mkdirp@>=0.5 0": version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -667,6 +697,11 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +sec@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sec/-/sec-2.0.0.tgz#4f5b82e7d1da27da6c7a40799e63caef21ce374f" + integrity sha512-uq35HWa7mG6YyojrduMXjF8UhOySEf3X0V1uMpSOBYUF09xAMnJaKNSmWXeE3mN7NfJTpNUkmGa6nIpEBMN8Xw== + "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -720,6 +755,13 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== +stream-transform@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-2.1.3.tgz#a1c3ecd72ddbf500aa8d342b0b9df38f5aa598e3" + integrity sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ== + dependencies: + mixme "^0.5.1" + string.prototype.padend@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.1.tgz#824c84265dbac46cade2b957b38b6a5d8d1683c5" @@ -764,6 +806,14 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +tasklist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tasklist/-/tasklist-5.0.0.tgz#0214f8a28c0fa9e042333154e3e3faa45c640404" + integrity sha512-qPB4J6pseXRqdxAFT1GhlvDPv4FHxWkXs8QVYQWIqusGwn7UXVKOoYu09DZuYWe1K7T5iusHfSoKrv8k9+RfxA== + dependencies: + csv "^5.5.0" + sec "^2.0.0" + "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" From 39332e7b6acbc74f9980d47fa1a028483ca7daf2 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 29 Nov 2021 08:33:28 +0100 Subject: [PATCH 0101/2210] Log only once (#137923) --- src/vs/platform/request/node/requestService.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 3be2146c33438..545b7cee58a75 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -43,6 +43,7 @@ export class RequestService extends Disposable implements IRequestService { private proxyUrl?: string; private strictSSL: boolean | undefined; private authorization?: string; + private shellEnvErrorLogged?: boolean; constructor( @IConfigurationService configurationService: IConfigurationService, @@ -69,7 +70,10 @@ export class RequestService extends Disposable implements IRequestService { try { shellEnv = await getResolvedShellEnv(this.logService, this.environmentService.args, process.env); } catch (error) { - this.logService.error('RequestService#request resolving shell environment failed', error); + if (!this.shellEnvErrorLogged) { + this.shellEnvErrorLogged = true; + this.logService.error('RequestService#request resolving shell environment failed', error); + } } const env = { From a5efd80ee02833365b3ccb8536de068534482c4a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 08:46:27 +0100 Subject: [PATCH 0102/2210] Revert "smoke - print task list when deleting workspace path fails" This reverts commit 368154bd0b42e3b198decd6e09144cfb30d431e7. --- test/smoke/package.json | 1 - test/smoke/src/main.ts | 13 +---------- test/smoke/yarn.lock | 50 ----------------------------------------- 3 files changed, 1 insertion(+), 63 deletions(-) diff --git a/test/smoke/package.json b/test/smoke/package.json index f6ba2fd84334c..60292ae8c6ce8 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -15,7 +15,6 @@ "ncp": "^2.0.0", "node-fetch": "^2.6.1", "rimraf": "3.0.2", - "tasklist": "^5.0.0", "vscode-test": "^1.6.1" }, "devDependencies": { diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 2b41e19510c2f..48aa7267e644b 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -17,7 +17,6 @@ import { ncp } from 'ncp'; import * as vscodetest from 'vscode-test'; import fetch from 'node-fetch'; import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger } from '../../automation'; -import { tasklist } from 'tasklist'; import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; import { setup as setupPreferencesTests } from './areas/preferences/preferences.test'; @@ -345,17 +344,7 @@ after(async function () { await promisify(ncp)(logsDir, destLogsDir); } - try { - await new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 10 }, error => error ? reject(error) : resolve())); - } catch (error) { - console.error(`Unable to delete smoke test workspace: ${error}`); - - if (process.platform === 'win32' && error.code === 'EPERM') { - console.log(await tasklist()); - } - - throw error; - } + await new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 50 }, error => error ? reject(error) : resolve())); }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 15522154f4447..28bb27266551d 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -195,31 +195,6 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -csv-generate@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-3.4.3.tgz#bc42d943b45aea52afa896874291da4b9108ffff" - integrity sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw== - -csv-parse@^4.16.3: - version "4.16.3" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.16.3.tgz#7ca624d517212ebc520a36873c3478fa66efbaf7" - integrity sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg== - -csv-stringify@^5.6.5: - version "5.6.5" - resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-5.6.5.tgz#c6d74badda4b49a79bf4e72f91cce1e33b94de00" - integrity sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A== - -csv@^5.5.0: - version "5.5.3" - resolved "https://registry.yarnpkg.com/csv/-/csv-5.5.3.tgz#cd26c1e45eae00ce6a9b7b27dcb94955ec95207d" - integrity sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g== - dependencies: - csv-generate "^3.4.3" - csv-parse "^4.16.3" - csv-stringify "^5.6.5" - stream-transform "^2.1.3" - debug@4: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" @@ -514,11 +489,6 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -mixme@^0.5.1: - version "0.5.4" - resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.4.tgz#8cb3bd0cd32a513c161bf1ca99d143f0bcf2eff3" - integrity sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw== - "mkdirp@>=0.5 0": version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -697,11 +667,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -sec@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sec/-/sec-2.0.0.tgz#4f5b82e7d1da27da6c7a40799e63caef21ce374f" - integrity sha512-uq35HWa7mG6YyojrduMXjF8UhOySEf3X0V1uMpSOBYUF09xAMnJaKNSmWXeE3mN7NfJTpNUkmGa6nIpEBMN8Xw== - "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -755,13 +720,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== -stream-transform@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-2.1.3.tgz#a1c3ecd72ddbf500aa8d342b0b9df38f5aa598e3" - integrity sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ== - dependencies: - mixme "^0.5.1" - string.prototype.padend@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.1.tgz#824c84265dbac46cade2b957b38b6a5d8d1683c5" @@ -806,14 +764,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -tasklist@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/tasklist/-/tasklist-5.0.0.tgz#0214f8a28c0fa9e042333154e3e3faa45c640404" - integrity sha512-qPB4J6pseXRqdxAFT1GhlvDPv4FHxWkXs8QVYQWIqusGwn7UXVKOoYu09DZuYWe1K7T5iusHfSoKrv8k9+RfxA== - dependencies: - csv "^5.5.0" - sec "^2.0.0" - "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" From c42793d0357ff9c6589cce79a847177fd42852ee Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 08:49:37 +0100 Subject: [PATCH 0103/2210] smoke - do not fail build when unable to delete workspace folder (workaround #137725) --- test/smoke/src/main.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 48aa7267e644b..fa6989e8b9d00 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -344,7 +344,12 @@ after(async function () { await promisify(ncp)(logsDir, destLogsDir); } - await new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 50 }, error => error ? reject(error) : resolve())); + try { + await new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 10 }, error => error ? reject(error) : resolve())); + } catch (error) { + // TODO@tyriar TODO@meganrogge https://github.com/microsoft/vscode/issues/137725 + console.error(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`); + } }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { From cbbba8e69a7807b3eaa71ad70ae60eb86a76de40 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 09:11:23 +0100 Subject: [PATCH 0104/2210] smoke - unique user data dir for stable tests too --- .../src/areas/workbench/data-migration.test.ts | 9 ++++----- test/smoke/src/main.ts | 2 +- test/smoke/src/utils.ts | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index d95a7c3e3a2b2..d0a158d220744 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Application, ApplicationOptions, Quality } from '../../../../automation'; -import { join } from 'path'; import { ParsedArgs } from 'minimist'; -import { afterSuite, startApp } from '../../utils'; +import { afterSuite, getRandomUserDataDir, startApp } from '../../utils'; -export function setup(opts: ParsedArgs, testDataPath: string) { +export function setup(opts: ParsedArgs) { describe('Data Migration (insiders -> insiders)', () => { @@ -90,7 +89,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) { this.retries(2); } - const userDataDir = join(testDataPath, 'd2'); // different data dir from the other tests + const userDataDir = getRandomUserDataDir(this.defaultOptions); const stableOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); stableOptions.codePath = stableCodePath; @@ -133,7 +132,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) { this.skip(); } - const userDataDir = join(testDataPath, 'd3'); // different data dir from the other tests + const userDataDir = getRandomUserDataDir(this.defaultOptions); const stableOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); stableOptions.codePath = stableCodePath; diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index fa6989e8b9d00..f526eb19bf49a 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -353,7 +353,7 @@ after(async function () { }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { - if (!opts.web) { setupDataMigrationTests(opts, testDataPath); } + if (!opts.web) { setupDataMigrationTests(opts); } if (!opts.web) { setupPreferencesTests(opts); } setupSearchTests(opts); setupNotebookTests(opts); diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index d4300a74be573..01f26878f9399 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -30,11 +30,11 @@ export async function startApp(args: minimist.ParsedArgs, options: ApplicationOp options = await optionsTransform({ ...options }); } - // https://github.com/microsoft/vscode/issues/34988 - const userDataPathSuffix = [...Array(8)].map(() => Math.random().toString(36)[3]).join(''); - const userDataDir = options.userDataDir.concat(`-${userDataPathSuffix}`); + const app = new Application({ + ...options, + userDataDir: getRandomUserDataDir(options) + }); - const app = new Application({ ...options, userDataDir }); await app.start(); if (args.log) { @@ -45,6 +45,16 @@ export async function startApp(args: minimist.ParsedArgs, options: ApplicationOp return app; } +export function getRandomUserDataDir(options: ApplicationOptions): string { + + // Pick a random user data dir suffix that is not + // too long to not run into max path length issues + // https://github.com/microsoft/vscode/issues/34988 + const userDataPathSuffix = [...Array(8)].map(() => Math.random().toString(36)[3]).join(''); + + return options.userDataDir.concat(`-${userDataPathSuffix}`); +} + export function afterSuite(opts: minimist.ParsedArgs, appFn?: () => Application | undefined, joinFn?: () => Promise) { after(async function () { const app: Application = appFn?.() ?? this.app; From fc55e8c38d9586701a0e347af6f807b4b9b4644b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 29 Nov 2021 09:08:47 +0100 Subject: [PATCH 0105/2210] do not show switch message for not installed --- .../contrib/extensions/browser/extensionsActions.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 713aa0fc365d9..fdeaa571293a1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2244,12 +2244,17 @@ export class ExtensionStatusAction extends ExtensionAction { this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('malicious tooltip', "This extension was reported to be problematic.")) }, true); return; } + if (this.extension.isUnsupported) { if (isBoolean(this.extension.isUnsupported)) { this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported tooltip', "This extension no longer supported.")) }, true); } else { const link = `[${this.extension.isUnsupported.preReleaseExtension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.isUnsupported.preReleaseExtension.id]))}`)})`; - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease tooltip', "This extension is no longer supported and is now part of the {0} extension as a pre-release version. We recommend that you switch to it.", link)) }, true); + if (this.extension.state === ExtensionState.Installed) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease switch tooltip', "This extension is no longer supported and is now part of the {0} extension as a pre-release version. We recommend that you switch to it.", link)) }, true); + } else { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease tooltip', "This extension is no longer supported and is now part of the {0} extension as a pre-release version.", link)) }, true); + } } return; } From 5bb653374f83e05b7dc0309122fe1a173434c2c1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 09:56:19 +0100 Subject: [PATCH 0106/2210] smoke - diagnose failing backup test --- test/smoke/package.json | 1 + .../areas/workbench/data-migration.test.ts | 24 +++++++++++++++++++ test/smoke/yarn.lock | 12 ++++++++++ 3 files changed, 37 insertions(+) diff --git a/test/smoke/package.json b/test/smoke/package.json index 60292ae8c6ce8..f134a185489ab 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -14,6 +14,7 @@ "mkdirp": "^1.0.4", "ncp": "^2.0.0", "node-fetch": "^2.6.1", + "readdirp": "^3.6.0", "rimraf": "3.0.2", "vscode-test": "^1.6.1" }, diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index d0a158d220744..ed780cc927338 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -5,7 +5,10 @@ import { Application, ApplicationOptions, Quality } from '../../../../automation'; import { ParsedArgs } from 'minimist'; +import * as readdirp from 'readdirp'; import { afterSuite, getRandomUserDataDir, startApp } from '../../utils'; +import { join } from 'path'; +import { readFileSync } from 'fs'; export function setup(opts: ParsedArgs) { @@ -158,12 +161,33 @@ export function setup(opts: ParsedArgs) { await stableApp.stop(); stableApp = undefined; + const backupsHome = join(userDataDir, 'Backups'); + console.log('Printing backup contents (after stable app stopped):'); + for await (const entry of readdirp(backupsHome)) { + try { + const contents = readFileSync(join(backupsHome, entry.path)).toString(); + console.log(`${entry.path}: ${contents.substring(0, contents.indexOf('\n'))}`); + } catch (error) { + console.log(`${entry.path}: Error reading file: ${error}`); + } + } + const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); insiderOptions.userDataDir = userDataDir; insidersApp = new Application(insiderOptions); await insidersApp.start(); + console.log('Printing backup contents (after insiders app started):'); + for await (const entry of readdirp(backupsHome)) { + try { + const contents = readFileSync(join(backupsHome, entry.path)).toString(); + console.log(`${entry.path}: ${contents.substring(0, contents.indexOf('\n'))}`); + } catch (error) { + console.log(`${entry.path}: Error reading file: ${error}`); + } + } + await insidersApp.workbench.editors.waitForTab(readmeMd, true); await insidersApp.workbench.quickaccess.openFile(readmeMd); await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 28bb27266551d..fe6bf509eea90 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -603,6 +603,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +picomatch@^2.2.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + pidtree@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" @@ -640,6 +645,13 @@ readable-stream@^2.0.2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readdirp@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + resolve@^1.10.0: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" From 12178a4cf72200f75f53d838f70e2ea4f54b9324 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 10:02:03 +0100 Subject: [PATCH 0107/2210] unit - exclude more tests for node.js runs (#137853) --- .../contrib/testing/test/common/testResultService.test.ts | 3 +-- test/unit/node/index.js | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index efecaa151040c..d1ef8c2808823 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -18,14 +18,13 @@ import { TestResultService } from 'vs/workbench/contrib/testing/common/testResul import { InMemoryResultStorage, ITestResultStorage } from 'vs/workbench/contrib/testing/common/testResultStorage'; import { Convert, getInitializedMainTestCollection, TestItemImpl, testStubs } from 'vs/workbench/contrib/testing/common/testStubs'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; -import { isNative, isElectron } from 'vs/base/common/platform'; export const emptyOutputController = () => new LiveOutputController( new Lazy(() => [newWriteableBufferStream(), Promise.resolve()]), () => Promise.resolve(bufferToStream(VSBuffer.alloc(0))), ); -((isNative && !isElectron) ? test.skip /* TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 */ : test)('Workbench - Test Results Service', () => { +suite('Workbench - Test Results Service', () => { const getLabelsIn = (it: Iterable) => [...it].map(t => t.item.label).sort(); const getChangeSummary = () => [...changed] .map(c => ({ reason: c.reason, label: c.item.item.label })) diff --git a/test/unit/node/index.js b/test/unit/node/index.js index 1be289b9a71f5..849c5a4bc0623 100644 --- a/test/unit/node/index.js +++ b/test/unit/node/index.js @@ -28,6 +28,8 @@ const excludeGlob = '**/{browser,electron-sandbox,electron-browser,electron-main const excludeModules = [ 'vs/platform/environment/test/node/nativeModules.test.js', 'vs/base/parts/storage/test/node/storage.test.js', + 'vs/workbench/contrib/testing/test/common/testResultStorage.test.js', // TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 + 'vs/workbench/contrib/testing/test/common/testResultService.test.js', // TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 'vs/platform/files/test/common/files.test.js' // TODO@bpasero enable once we ship Electron 16 ] From 687521092d83f2fdd0492774050100985b267684 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 10:12:42 +0100 Subject: [PATCH 0108/2210] smoke - print running tasks after smoke test run --- build/azure-pipelines/win32/listprocesses.bat | 3 +++ build/azure-pipelines/win32/product-build-win32.yml | 5 +++++ test/unit/node/index.js | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 build/azure-pipelines/win32/listprocesses.bat diff --git a/build/azure-pipelines/win32/listprocesses.bat b/build/azure-pipelines/win32/listprocesses.bat new file mode 100644 index 0000000000000..e184b37fe5697 --- /dev/null +++ b/build/azure-pipelines/win32/listprocesses.bat @@ -0,0 +1,3 @@ +echo "------------------------------------" +tasklist +echo "------------------------------------" diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 8d3e0aab72b78..247e23675a83f 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -234,6 +234,11 @@ steps: timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + exec {.\build\azure-pipelines\win32\listprocesses.bat } + displayName: List tasks after smoke test run + - task: PublishPipelineArtifact@0 inputs: artifactName: crash-dump-windows-$(VSCODE_ARCH) diff --git a/test/unit/node/index.js b/test/unit/node/index.js index 849c5a4bc0623..168362a116ce2 100644 --- a/test/unit/node/index.js +++ b/test/unit/node/index.js @@ -26,8 +26,8 @@ const optimist = require('optimist') const TEST_GLOB = '**/test/**/*.test.js'; const excludeGlob = '**/{browser,electron-sandbox,electron-browser,electron-main,editor/contrib}/**/*.test.js'; const excludeModules = [ - 'vs/platform/environment/test/node/nativeModules.test.js', - 'vs/base/parts/storage/test/node/storage.test.js', + 'vs/platform/environment/test/node/nativeModules.test.js', // native modules are compiled against Electron and this test would fail with node.js + 'vs/base/parts/storage/test/node/storage.test.js', // same as above, due to direct dependency to sqlite native module 'vs/workbench/contrib/testing/test/common/testResultStorage.test.js', // TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 'vs/workbench/contrib/testing/test/common/testResultService.test.js', // TODO@connor4312 https://github.com/microsoft/vscode/issues/137853 'vs/platform/files/test/common/files.test.js' // TODO@bpasero enable once we ship Electron 16 From a2cac5d9276323746e1e4c3d4815cdfd4cf34619 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 10:34:12 +0100 Subject: [PATCH 0109/2210] Add dom events to `TextAreaWrapper` --- src/vs/base/browser/dom.ts | 18 +++ .../browser/controller/textAreaHandler.ts | 5 +- .../browser/controller/textAreaInput.ts | 107 ++++++++++++------ .../test/browser/controller/imeTester.ts | 4 +- 4 files changed, 97 insertions(+), 37 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 1f20d93442f42..8e98cb9ca81fd 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -149,6 +149,24 @@ export function addDisposableNonBubblingPointerOutListener(node: Element, handle }); } +export function createEventEmitter(target: HTMLElement, type: K, options?: boolean | AddEventListenerOptions): Emitter { + let domListener: DomListener | null = null; + const handler = (e: HTMLElementEventMap[K]) => result.fire(e); + const onFirstListenerAdd = () => { + if (!domListener) { + domListener = new DomListener(target, type, handler, options); + } + }; + const onLastListenerRemove = () => { + if (domListener) { + domListener.dispose(); + domListener = null; + } + }; + const result = new Emitter({ onFirstListenerAdd, onLastListenerRemove }); + return result; +} + interface IRequestAnimationFrame { (callback: (time: number) => void): number; } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 323517cac2d05..cb7e0678e81be 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -11,7 +11,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { Configuration } from 'vs/editor/browser/config/configuration'; -import { CopyOptions, ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput, ClipboardDataToCopy } from 'vs/editor/browser/controller/textAreaInput'; +import { CopyOptions, ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput, ClipboardDataToCopy, TextAreaWrapper } from 'vs/editor/browser/controller/textAreaInput'; import { ISimpleModel, ITypeData, PagedScreenReaderStrategy, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/textAreaState'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; @@ -226,7 +226,8 @@ export class TextAreaHandler extends ViewPart { } }; - this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, this.textArea)); + const textAreaWrapper = this._register(new TextAreaWrapper(this.textArea)); + this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, textAreaWrapper)); this._register(this._textAreaInput.onKeyDown((e: IKeyboardEvent) => { this._viewController.emitKeyDown(e); diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 5105905403911..ab3ca2952e776 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -6,7 +6,7 @@ import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -61,11 +61,6 @@ export interface ITextAreaInputHost { deduceModelPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position; } -interface CompositionEvent extends UIEvent { - readonly data: string; - readonly locale: string; -} - interface InMemoryClipboardMetadata { lastCopiedValue: string; data: ClipboardStoredMetadata; @@ -103,6 +98,28 @@ export interface ICompositionStartEvent { revealDeltaColumns: number; } +export interface ICompleteTextAreaWrapper extends ITextAreaWrapper { + readonly onKeyDown: Event; + readonly onKeyUp: Event; + readonly onCompositionStart: Event; + readonly onCompositionUpdate: Event; + readonly onCompositionEnd: Event; + readonly onBeforeInput: Event; + readonly onInput: Event; + readonly onCut: Event; + readonly onCopy: Event; + readonly onPaste: Event; + readonly onFocus: Event; + readonly onBlur: Event; + readonly onSyntheticTap: Event; + + setIgnoreSelectionChangeTime(reason: string): void; + getIgnoreSelectionChangeTime(): number; + resetSelectionChangeTime(): void; + + hasFocus(): boolean; +} + /** * Writes screen reader content to the textarea and is able to analyze its input events to generate: * - onCut @@ -149,7 +166,7 @@ export class TextAreaInput extends Disposable { // --- private readonly _host: ITextAreaInputHost; - private readonly _textArea: TextAreaWrapper; + private readonly _textArea: ICompleteTextAreaWrapper; private readonly _asyncTriggerCut: RunOnceScheduler; private readonly _asyncFocusGainWriteScreenReaderContent: RunOnceScheduler; @@ -160,10 +177,10 @@ export class TextAreaInput extends Disposable { private _isDoingComposition: boolean; private _nextCommand: ReadFromTextArea; - constructor(host: ITextAreaInputHost, private textArea: FastDomNode) { + constructor(host: ITextAreaInputHost, textArea: ICompleteTextAreaWrapper) { super(); this._host = host; - this._textArea = this._register(new TextAreaWrapper(textArea)); + this._textArea = textArea; this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0)); this._asyncFocusGainWriteScreenReaderContent = this._register(new RunOnceScheduler(() => this.writeScreenReaderContent('asyncFocusGain'), 0)); @@ -177,7 +194,8 @@ export class TextAreaInput extends Disposable { let lastKeyDown: IKeyboardEvent | null = null; - this._register(dom.addStandardDisposableListener(textArea.domNode, 'keydown', (e: IKeyboardEvent) => { + this._register(this._textArea.onKeyDown((_e) => { + const e = new StandardKeyboardEvent(_e); if (e.keyCode === KeyCode.KEY_IN_COMPOSITION || (this._isDoingComposition && e.keyCode === KeyCode.Backspace)) { // Stop propagation for keyDown events if the IME is processing key input @@ -194,11 +212,12 @@ export class TextAreaInput extends Disposable { this._onKeyDown.fire(e); })); - this._register(dom.addStandardDisposableListener(textArea.domNode, 'keyup', (e: IKeyboardEvent) => { + this._register(this._textArea.onKeyUp((_e) => { + const e = new StandardKeyboardEvent(_e); this._onKeyUp.fire(e); })); - this._register(dom.addDisposableListener(textArea.domNode, 'compositionstart', (e: CompositionEvent) => { + this._register(this._textArea.onCompositionStart((e) => { if (_debugComposition) { console.log(`[compositionstart]`, e); } @@ -277,7 +296,7 @@ export class TextAreaInput extends Disposable { return [newState, typeInput]; }; - this._register(dom.addDisposableListener(textArea.domNode, 'compositionupdate', (e: CompositionEvent) => { + this._register(this._textArea.onCompositionUpdate((e) => { if (_debugComposition) { console.log(`[compositionupdate]`, e); } @@ -298,7 +317,7 @@ export class TextAreaInput extends Disposable { this._onCompositionUpdate.fire(e); })); - this._register(dom.addDisposableListener(textArea.domNode, 'compositionend', (e: CompositionEvent) => { + this._register(this._textArea.onCompositionEnd((e) => { if (_debugComposition) { console.log(`[compositionend]`, e); } @@ -335,7 +354,7 @@ export class TextAreaInput extends Disposable { this._onCompositionEnd.fire(); })); - this._register(dom.addDisposableListener(textArea.domNode, 'input', () => { + this._register(this._textArea.onInput((e) => { // Pretend here we touched the text area, as the `input` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received input event'); @@ -365,7 +384,7 @@ export class TextAreaInput extends Disposable { // --- Clipboard operations - this._register(dom.addDisposableListener(textArea.domNode, 'cut', (e: ClipboardEvent) => { + this._register(this._textArea.onCut((e) => { // Pretend here we touched the text area, as the `cut` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received cut event'); @@ -374,11 +393,11 @@ export class TextAreaInput extends Disposable { this._asyncTriggerCut.schedule(); })); - this._register(dom.addDisposableListener(textArea.domNode, 'copy', (e: ClipboardEvent) => { + this._register(this._textArea.onCopy((e) => { this._ensureClipboardGetsEditorSelection(e); })); - this._register(dom.addDisposableListener(textArea.domNode, 'paste', (e: ClipboardEvent) => { + this._register(this._textArea.onPaste((e) => { // Pretend here we touched the text area, as the `paste` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received paste event'); @@ -397,7 +416,7 @@ export class TextAreaInput extends Disposable { } })); - this._register(dom.addDisposableListener(textArea.domNode, 'focus', () => { + this._register(this._textArea.onFocus(() => { const hadFocus = this._hasFocus; this._setHasFocus(true); @@ -408,7 +427,7 @@ export class TextAreaInput extends Disposable { this._asyncFocusGainWriteScreenReaderContent.schedule(); } })); - this._register(dom.addDisposableListener(textArea.domNode, 'blur', () => { + this._register(this._textArea.onBlur(() => { if (this._isDoingComposition) { // See https://github.com/microsoft/vscode/issues/112621 // where compositionend is not triggered when the editor @@ -425,7 +444,7 @@ export class TextAreaInput extends Disposable { } this._setHasFocus(false); })); - this._register(dom.addDisposableListener(textArea.domNode, TextAreaSyntethicEvents.Tap, () => { + this._register(this._textArea.onSyntheticTap(() => { if (browser.isAndroid && this._isDoingComposition) { // on Android, tapping does not cancel the current composition, so the // textarea is stuck showing the old composition @@ -547,14 +566,7 @@ export class TextAreaInput extends Disposable { } public refreshFocusState(): void { - const shadowRoot = dom.getShadowRoot(this.textArea.domNode); - if (shadowRoot) { - this._setHasFocus(shadowRoot.activeElement === this.textArea.domNode); - } else if (dom.isInDOM(this.textArea.domNode)) { - this._setHasFocus(document.activeElement === this.textArea.domNode); - } else { - this._setHasFocus(false); - } + this._setHasFocus(this._textArea.hasFocus()); } private _setHasFocus(newHasFocus: boolean): void { @@ -686,15 +698,44 @@ class ClipboardEventUtils { } } -class TextAreaWrapper extends Disposable implements ITextAreaWrapper { +export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrapper { + + public readonly onKeyDown = this._register(dom.createEventEmitter(this._actual.domNode, 'keydown')).event; + public readonly onKeyUp = this._register(dom.createEventEmitter(this._actual.domNode, 'keyup')).event; + public readonly onCompositionStart = this._register(dom.createEventEmitter(this._actual.domNode, 'compositionstart')).event; + public readonly onCompositionUpdate = this._register(dom.createEventEmitter(this._actual.domNode, 'compositionupdate')).event; + public readonly onCompositionEnd = this._register(dom.createEventEmitter(this._actual.domNode, 'compositionend')).event; + public readonly onBeforeInput = this._register(dom.createEventEmitter(this._actual.domNode, 'beforeinput')).event; + public readonly onInput = >this._register(dom.createEventEmitter(this._actual.domNode, 'input')).event; + public readonly onCut = this._register(dom.createEventEmitter(this._actual.domNode, 'cut')).event; + public readonly onCopy = this._register(dom.createEventEmitter(this._actual.domNode, 'copy')).event; + public readonly onPaste = this._register(dom.createEventEmitter(this._actual.domNode, 'paste')).event; + public readonly onFocus = this._register(dom.createEventEmitter(this._actual.domNode, 'focus')).event; + public readonly onBlur = this._register(dom.createEventEmitter(this._actual.domNode, 'blur')).event; + + private _onSyntheticTap = this._register(new Emitter()); + public readonly onSyntheticTap: Event = this._onSyntheticTap.event; - private readonly _actual: FastDomNode; private _ignoreSelectionChangeTime: number; - constructor(_textArea: FastDomNode) { + constructor( + private readonly _actual: FastDomNode + ) { super(); - this._actual = _textArea; this._ignoreSelectionChangeTime = 0; + + this._register(dom.addDisposableListener(this._actual.domNode, TextAreaSyntethicEvents.Tap, () => this._onSyntheticTap.fire())); + } + + public hasFocus(): boolean { + const shadowRoot = dom.getShadowRoot(this._actual.domNode); + if (shadowRoot) { + return shadowRoot.activeElement === this._actual.domNode; + } else if (dom.isInDOM(this._actual.domNode)) { + return document.activeElement === this._actual.domNode; + } else { + return false; + } } public setIgnoreSelectionChangeTime(reason: string): void { diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index cb6908d4a3483..915379c564310 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; +import { ITextAreaInputHost, TextAreaInput, TextAreaWrapper } from 'vs/editor/browser/controller/textAreaInput'; import { ISimpleModel, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -111,7 +111,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string } }; - let handler = new TextAreaInput(textAreaInputHost, createFastDomNode(input)); + let handler = new TextAreaInput(textAreaInputHost, new TextAreaWrapper(createFastDomNode(input))); let output = document.createElement('pre'); output.className = 'output'; From 414c15079b689d25f9b4f707d6cca076b0acff2e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 29 Nov 2021 11:11:56 +0100 Subject: [PATCH 0110/2210] update endgame notebooks --- .vscode/notebooks/endgame.github-issues | 2 +- .vscode/notebooks/my-endgame.github-issues | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index a242c4053ae62..40dcb6c71de12 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\n\n$MILESTONE=milestone:\"October 2021\"" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\n\n$MILESTONE=milestone:\"November 2021\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index f8a9349d7f1c4..ffe2a29d04ad0 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\n\n$MILESTONE=milestone:\"October 2021\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\n\n$MILESTONE=milestone:\"November 2021\"\n\n$MINE=assignee:@me" }, { "kind": 1, From 3d8b5674c769c885beee85546625d419ffafc10a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Nov 2021 11:25:04 +0100 Subject: [PATCH 0111/2210] [html] clean up request service: add CustomDataRequestService #135459 --- .../client/src/customData.ts | 20 ++++- .../client/src/htmlClient.ts | 14 +++- .../client/src/node/htmlClientMain.ts | 4 +- .../client/src/node/nodeFs.ts | 19 +---- .../client/src/requests.ts | 82 ++----------------- .../html-language-features/package.json | 3 +- .../server/src/customData.ts | 4 +- .../server/src/htmlServer.ts | 34 +++++--- .../server/src/modes/languageModes.ts | 4 +- .../server/src/node/htmlServerMain.ts | 4 +- .../server/src/node/nodeFs.ts | 19 +---- .../server/src/requests.ts | 35 ++------ .../server/src/test/completions.test.ts | 4 +- .../server/src/test/folding.test.ts | 4 +- .../server/src/test/formatting.test.ts | 4 +- .../server/src/test/rename.test.ts | 6 +- .../server/src/test/selectionRanges.test.ts | 4 +- .../server/src/test/semanticTokens.test.ts | 4 +- extensions/html-language-features/yarn.lock | 5 ++ 19 files changed, 95 insertions(+), 178 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index 02c5b0d0a7d08..af8652af2bcd6 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode'; -import { resolvePath, joinPath } from './requests'; +import { Runtime } from './htmlClient'; +import { Utils } from 'vscode-uri'; -export function getCustomDataSource(toDispose: Disposable[]) { +export function getCustomDataSource(runtime: Runtime, toDispose: Disposable[]) { let pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); let pathsInExtensions = getCustomDataPathsFromAllExtensions(); @@ -40,6 +41,17 @@ export function getCustomDataSource(toDispose: Disposable[]) { }, get onDidChange() { return onChange.event; + }, + getContent(uriString: string): Thenable { + const uri = Uri.parse(uriString); + if (pathsInExtensions.has(uriString)) { + return workspace.fs.readFile(uri).then(buffer => { + return new runtime.TextDecoder().decode(buffer); + }); + } + return workspace.openTextDocument(uri).then(doc => { + return doc.getText(); + }); } }; } @@ -64,7 +76,7 @@ function getCustomDataPathsInAllWorkspaces(): Set { if (typeof uriOrPath === 'string') { if (!isURI(uriOrPath)) { // path in the workspace - dataPaths.add(resolvePath(rootFolder, uriOrPath).toString()); + dataPaths.add(Utils.resolvePath(rootFolder, uriOrPath).toString()); } else { // external uri dataPaths.add(uriOrPath); @@ -100,7 +112,7 @@ function getCustomDataPathsFromAllExtensions(): Set { for (const uriOrPath of customData) { if (!isURI(uriOrPath)) { // relative path in an extension - dataPaths.add(joinPath(extension.extensionUri, uriOrPath).toString()); + dataPaths.add(Uri.joinPath(extension.extensionUri, uriOrPath).toString()); } else { // external uri dataPaths.add(uriOrPath); diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index bfb241c22506a..472977e652f6e 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -16,13 +16,17 @@ import { DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, NotificationType, CommonLanguageClient } from 'vscode-languageclient'; import { activateTagClosing } from './tagClosing'; -import { RequestService, serveFileSystemRequests } from './requests'; +import { FileSystemProvider, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; namespace CustomDataChangedNotification { export const type: NotificationType = new NotificationType('html/customDataChanged'); } +namespace CustomDataContent { + export const type: RequestType = new RequestType('html/customDataContent'); +} + namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } @@ -56,7 +60,7 @@ export type LanguageClientConstructor = (name: string, description: string, clie export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string; } }; - fs?: RequestService; + fileFs?: FileSystemProvider; telemetry?: TelemetryReporter; readonly timer: { setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; @@ -73,8 +77,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua let rangeFormatting: Disposable | undefined = undefined; - const customDataSource = getCustomDataSource(context.subscriptions); - // Options to control the language client let clientOptions: LanguageClientOptions = { documentSelector, @@ -122,10 +124,14 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua toDispose.push(serveFileSystemRequests(client, runtime)); + const customDataSource = getCustomDataSource(runtime, context.subscriptions); + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); }); + client.onRequest(CustomDataContent.type, customDataSource.getContent); + let tagRequestor = (document: TextDocument, position: Position) => { let param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position); diff --git a/extensions/html-language-features/client/src/node/htmlClientMain.ts b/extensions/html-language-features/client/src/node/htmlClientMain.ts index d402ee31e7954..be25363eff4f9 100644 --- a/extensions/html-language-features/client/src/node/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/node/htmlClientMain.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getNodeFSRequestService } from './nodeFs'; +import { getNodeFileFS } from './nodeFs'; import { Disposable, ExtensionContext } from 'vscode'; import { startClient, LanguageClientConstructor } from '../htmlClient'; import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; @@ -44,7 +44,7 @@ export function activate(context: ExtensionContext) { } }; - startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder, telemetry, timer }); + startClient(context, newLanguageClient, { fileFs: getNodeFileFS(), TextDecoder, telemetry, timer }); } interface IPackageInfo { diff --git a/extensions/html-language-features/client/src/node/nodeFs.ts b/extensions/html-language-features/client/src/node/nodeFs.ts index c13ef2e1c08d5..46a3aeb9de459 100644 --- a/extensions/html-language-features/client/src/node/nodeFs.ts +++ b/extensions/html-language-features/client/src/node/nodeFs.ts @@ -5,28 +5,15 @@ import * as fs from 'fs'; import { Uri } from 'vscode'; -import { getScheme, RequestService, FileType } from '../requests'; +import { FileSystemProvider, FileType } from '../requests'; -export function getNodeFSRequestService(): RequestService { +export function getNodeFileFS(): FileSystemProvider { function ensureFileUri(location: string) { - if (getScheme(location) !== 'file') { + if (!location.startsWith('file:')) { throw new Error('fileRequestService can only handle file URLs'); } } return { - getContent(location: string, encoding?: string) { - ensureFileUri(location); - return new Promise((c, e) => { - const uri = Uri.parse(location); - fs.readFile(uri.fsPath, encoding, (err, buf) => { - if (err) { - return e(err); - } - c(buf.toString()); - - }); - }); - }, stat(location: string) { ensureFileUri(location); return new Promise((c, e) => { diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index 867ad6fc7d442..ba124e28cd7cf 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -7,9 +7,6 @@ import { Uri, workspace, Disposable } from 'vscode'; import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; import { Runtime } from './htmlClient'; -export namespace FsContentRequest { - export const type: RequestType<{ uri: string; encoding?: string; }, string, any> = new RequestType('fs/content'); -} export namespace FsStatRequest { export const type: RequestType = new RequestType('fs/stat'); } @@ -20,26 +17,17 @@ export namespace FsReadDirRequest { export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime): Disposable { const disposables = []; - disposables.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { - const uri = Uri.parse(param.uri); - if (uri.scheme === 'file' && runtime.fs) { - return runtime.fs.getContent(param.uri); - } - return workspace.fs.readFile(uri).then(buffer => { - return new runtime.TextDecoder(param.encoding).decode(buffer); - }); - })); disposables.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); - if (uri.scheme === 'file' && runtime.fs) { - return runtime.fs.readDirectory(uriString); + if (uri.scheme === 'file' && runtime.fileFs) { + return runtime.fileFs.readDirectory(uriString); } return workspace.fs.readDirectory(uri); })); disposables.push(client.onRequest(FsStatRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); - if (uri.scheme === 'file' && runtime.fs) { - return runtime.fs.stat(uriString); + if (uri.scheme === 'file' && runtime.fileFs) { + return runtime.fileFs.stat(uriString); } return workspace.fs.stat(uri); })); @@ -84,67 +72,7 @@ export interface FileStat { size: number; } -export interface RequestService { - getContent(uri: string, encoding?: string): Promise; - +export interface FileSystemProvider { stat(uri: string): Promise; readDirectory(uri: string): Promise<[string, FileType][]>; } - -export function getScheme(uri: string) { - return uri.substr(0, uri.indexOf(':')); -} - -export function dirname(uri: string) { - const lastIndexOfSlash = uri.lastIndexOf('/'); - return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : ''; -} - -export function basename(uri: string) { - const lastIndexOfSlash = uri.lastIndexOf('/'); - return uri.substr(lastIndexOfSlash + 1); -} - -const Slash = '/'.charCodeAt(0); -const Dot = '.'.charCodeAt(0); - -export function isAbsolutePath(path: string) { - return path.charCodeAt(0) === Slash; -} - -export function resolvePath(uri: Uri, path: string): Uri { - if (isAbsolutePath(path)) { - return uri.with({ path: normalizePath(path.split('/')) }); - } - return joinPath(uri, path); -} - -export function normalizePath(parts: string[]): string { - const newParts: string[] = []; - for (const part of parts) { - if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) { - // ignore - } else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) { - newParts.pop(); - } else { - newParts.push(part); - } - } - if (parts.length > 1 && parts[parts.length - 1].length === 0) { - newParts.push(''); - } - let res = newParts.join('/'); - if (parts[0].length === 0) { - res = '/' + res; - } - return res; -} - - -export function joinPath(uri: Uri, ...paths: string[]): Uri { - const parts = uri.path.split('/'); - for (let path of paths) { - parts.push(...path.split('/')); - } - return uri.with({ path: normalizePath(parts) }); -} diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 07bbfe29e9abd..1a8722e02995b 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -257,7 +257,8 @@ "dependencies": { "vscode-extension-telemetry": "0.4.3", "vscode-languageclient": "^7.0.0", - "vscode-nls": "^5.0.0" + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" }, "devDependencies": { "@types/node": "14.x" diff --git a/extensions/html-language-features/server/src/customData.ts b/extensions/html-language-features/server/src/customData.ts index 3ef347a23d2ef..08e40bf2db3ee 100644 --- a/extensions/html-language-features/server/src/customData.ts +++ b/extensions/html-language-features/server/src/customData.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IHTMLDataProvider, newHTMLDataProvider } from 'vscode-html-languageservice'; -import { RequestService } from './requests'; +import { CustomDataRequestService } from './htmlServer'; -export function fetchHTMLDataProviders(dataPaths: string[], requestService: RequestService): Promise { +export function fetchHTMLDataProviders(dataPaths: string[], requestService: CustomDataRequestService): Promise { const providers = dataPaths.map(async p => { try { const content = await requestService.getContent(p); diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 425e73c806aa2..8a053592ba031 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -24,12 +24,16 @@ import { getFoldingRanges } from './modes/htmlFolding'; import { fetchHTMLDataProviders } from './customData'; import { getSelectionRanges } from './modes/selectionRanges'; import { SemanticTokenProvider, newSemanticTokenProvider } from './modes/semanticTokens'; -import { RequestService, getRequestService } from './requests'; +import { FileSystemProvider, getFileSystemProvider } from './requests'; namespace CustomDataChangedNotification { export const type: NotificationType = new NotificationType('html/customDataChanged'); } +namespace CustomDataContent { + export const type: RequestType = new RequestType('html/customDataContent'); +} + namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } @@ -47,8 +51,7 @@ namespace SemanticTokenLegendRequest { } export interface RuntimeEnvironment { - file?: RequestService; - http?: RequestService + fileFs?: FileSystemProvider; configureHttpRequests?(proxy: string, strictSSL: boolean): void; readonly timer: { setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable; @@ -56,6 +59,12 @@ export interface RuntimeEnvironment { } } + +export interface CustomDataRequestService { + getContent(uri: string): Promise; +} + + export function startServer(connection: Connection, runtime: RuntimeEnvironment) { // Create a text document manager. @@ -74,10 +83,11 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let workspaceFoldersSupport = false; let foldingRangeLimit = Number.MAX_VALUE; - const notReady = () => Promise.reject('Not Ready'); - let requestService: RequestService = { getContent: notReady, stat: notReady, readDirectory: notReady }; - - + const customDataRequestService : CustomDataRequestService = { + getContent(uri: string) { + return connection.sendRequest(CustomDataContent.type, uri); + } + }; let globalSettings: Settings = {}; let documentSettings: { [key: string]: Thenable } = {}; @@ -113,17 +123,19 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } } - requestService = getRequestService(initializationOptions?.handledSchemas || ['file'], connection, runtime); + const handledSchemas = initializationOptions?.handledSchemas as string[] ?? ['file']; + + const fileSystemProvider = getFileSystemProvider(handledSchemas, connection, runtime); const workspace = { get settings() { return globalSettings; }, get folders() { return workspaceFolders; } }; - languageModes = getLanguageModes(initializationOptions?.embeddedLanguages || { css: true, javascript: true }, workspace, params.capabilities, requestService); + languageModes = getLanguageModes(initializationOptions?.embeddedLanguages || { css: true, javascript: true }, workspace, params.capabilities, fileSystemProvider); const dataPaths: string[] = initializationOptions?.dataPaths || []; - fetchHTMLDataProviders(dataPaths, requestService).then(dataProviders => { + fetchHTMLDataProviders(dataPaths, customDataRequestService).then(dataProviders => { languageModes.updateDataProviders(dataProviders); }); @@ -567,7 +579,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.onNotification(CustomDataChangedNotification.type, dataPaths => { - fetchHTMLDataProviders(dataPaths, requestService).then(dataProviders => { + fetchHTMLDataProviders(dataPaths, customDataRequestService).then(dataProviders => { languageModes.updateDataProviders(dataProviders); }); }); diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts index eda0dd8376081..1e188e7c0b744 100644 --- a/extensions/html-language-features/server/src/modes/languageModes.ts +++ b/extensions/html-language-features/server/src/modes/languageModes.ts @@ -21,7 +21,7 @@ import { getCSSMode } from './cssMode'; import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport'; import { getHTMLMode } from './htmlMode'; import { getJavaScriptMode } from './javascriptMode'; -import { RequestService } from '../requests'; +import { FileSystemProvider } from '../requests'; export { WorkspaceFolder, CompletionItem, CompletionList, CompletionItemKind, Definition, Diagnostic, DocumentHighlight, DocumentHighlightKind, @@ -97,7 +97,7 @@ export interface LanguageModeRange extends Range { attributeValue?: boolean; } -export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }, workspace: Workspace, clientCapabilities: ClientCapabilities, requestService: RequestService): LanguageModes { +export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }, workspace: Workspace, clientCapabilities: ClientCapabilities, requestService: FileSystemProvider): LanguageModes { const htmlLanguageService = getHTMLLanguageService({ clientCapabilities, fileSystemProvider: requestService }); const cssLanguageService = getCSSLanguageService({ clientCapabilities, fileSystemProvider: requestService }); diff --git a/extensions/html-language-features/server/src/node/htmlServerMain.ts b/extensions/html-language-features/server/src/node/htmlServerMain.ts index faf82b3c40c13..0367e11a2209f 100644 --- a/extensions/html-language-features/server/src/node/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/node/htmlServerMain.ts @@ -6,7 +6,7 @@ import { createConnection, Connection, Disposable } from 'vscode-languageserver/node'; import { formatError } from '../utils/runner'; import { RuntimeEnvironment, startServer } from '../htmlServer'; -import { getNodeFSRequestService } from './nodeFs'; +import { getNodeFileFS } from './nodeFs'; // Create a connection for the server. @@ -30,7 +30,7 @@ const runtime: RuntimeEnvironment = { return { dispose: () => clearTimeout(handle) }; } }, - file: getNodeFSRequestService() + fileFs: getNodeFileFS() }; startServer(connection, runtime); diff --git a/extensions/html-language-features/server/src/node/nodeFs.ts b/extensions/html-language-features/server/src/node/nodeFs.ts index c7b1301296d4a..9bab4cf2913fd 100644 --- a/extensions/html-language-features/server/src/node/nodeFs.ts +++ b/extensions/html-language-features/server/src/node/nodeFs.ts @@ -3,32 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RequestService, getScheme } from '../requests'; +import { FileSystemProvider, getScheme } from '../requests'; import { URI as Uri } from 'vscode-uri'; import * as fs from 'fs'; import { FileType } from 'vscode-css-languageservice'; -export function getNodeFSRequestService(): RequestService { +export function getNodeFileFS(): FileSystemProvider { function ensureFileUri(location: string) { if (getScheme(location) !== 'file') { - throw new Error('fileRequestService can only handle file URLs'); + throw new Error('fileSystemProvider can only handle file URLs'); } } return { - getContent(location: string, encoding?: string) { - ensureFileUri(location); - return new Promise((c, e) => { - const uri = Uri.parse(location); - fs.readFile(uri.fsPath, encoding, (err, buf) => { - if (err) { - return e(err); - } - c(buf.toString()); - - }); - }); - }, stat(location: string) { ensureFileUri(location); return new Promise((c, e) => { diff --git a/extensions/html-language-features/server/src/requests.ts b/extensions/html-language-features/server/src/requests.ts index 8d741faebf373..9ece22ac66e96 100644 --- a/extensions/html-language-features/server/src/requests.ts +++ b/extensions/html-language-features/server/src/requests.ts @@ -7,9 +7,6 @@ import { URI } from 'vscode-uri'; import { RequestType, Connection } from 'vscode-languageserver'; import { RuntimeEnvironment } from './htmlServer'; -export namespace FsContentRequest { - export const type: RequestType<{ uri: string; encoding?: string; }, string, any> = new RequestType('fs/content'); -} export namespace FsStatRequest { export const type: RequestType = new RequestType('fs/stat'); } @@ -56,45 +53,27 @@ export interface FileStat { size: number; } -export interface RequestService { - getContent(uri: string, encoding?: string): Promise; - +export interface FileSystemProvider { stat(uri: string): Promise; readDirectory(uri: string): Promise<[string, FileType][]>; } -export function getRequestService(handledSchemas: string[], connection: Connection, runtime: RuntimeEnvironment): RequestService { - const builtInHandlers: { [protocol: string]: RequestService | undefined } = {}; - for (let protocol of handledSchemas) { - if (protocol === 'file') { - builtInHandlers[protocol] = runtime.file; - } else if (protocol === 'http' || protocol === 'https') { - builtInHandlers[protocol] = runtime.http; - } - } +export function getFileSystemProvider(handledSchemas: string[], connection: Connection, runtime: RuntimeEnvironment): FileSystemProvider { + const fileFs = runtime.fileFs && handledSchemas.indexOf('file') !== -1 ? runtime.fileFs : undefined; return { async stat(uri: string): Promise { - const handler = builtInHandlers[getScheme(uri)]; - if (handler) { - return handler.stat(uri); + if (fileFs && uri.startsWith('file:')) { + return fileFs.stat(uri); } const res = await connection.sendRequest(FsStatRequest.type, uri.toString()); return res; }, readDirectory(uri: string): Promise<[string, FileType][]> { - const handler = builtInHandlers[getScheme(uri)]; - if (handler) { - return handler.readDirectory(uri); + if (fileFs && uri.startsWith('file:')) { + return fileFs.readDirectory(uri); } return connection.sendRequest(FsReadDirRequest.type, uri.toString()); - }, - getContent(uri: string, encoding?: string): Promise { - const handler = builtInHandlers[getScheme(uri)]; - if (handler) { - return handler.getContent(uri, encoding); - } - return connection.sendRequest(FsContentRequest.type, { uri: uri.toString(), encoding }); } }; } diff --git a/extensions/html-language-features/server/src/test/completions.test.ts b/extensions/html-language-features/server/src/test/completions.test.ts index aaf3cb5ec6b84..b64d7dee8604c 100644 --- a/extensions/html-language-features/server/src/test/completions.test.ts +++ b/extensions/html-language-features/server/src/test/completions.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as path from 'path'; import { URI } from 'vscode-uri'; import { getLanguageModes, WorkspaceFolder, TextDocument, CompletionList, CompletionItemKind, ClientCapabilities, TextEdit } from '../modes/languageModes'; -import { getNodeFSRequestService } from '../node/nodeFs'; +import { getNodeFileFS } from '../node/nodeFs'; import { getDocumentContext } from '../utils/documentContext'; export interface ItemDescription { label: string; @@ -59,7 +59,7 @@ export async function testCompletionFor(value: string, expected: { count?: numbe let position = document.positionAt(offset); const context = getDocumentContext(uri, workspace.folders); - const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService()); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFileFS()); const mode = languageModes.getModeAtPosition(document, position)!; let list = await mode.doComplete!(document, position, context); diff --git a/extensions/html-language-features/server/src/test/folding.test.ts b/extensions/html-language-features/server/src/test/folding.test.ts index 7bf90bb10d544..44aaea9026cf5 100644 --- a/extensions/html-language-features/server/src/test/folding.test.ts +++ b/extensions/html-language-features/server/src/test/folding.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import { getFoldingRanges } from '../modes/htmlFolding'; import { TextDocument, getLanguageModes } from '../modes/languageModes'; import { ClientCapabilities } from 'vscode-css-languageservice'; -import { getNodeFSRequestService } from '../node/nodeFs'; +import { getNodeFileFS } from '../node/nodeFs'; interface ExpectedIndentRange { startLine: number; @@ -22,7 +22,7 @@ async function assertRanges(lines: string[], expected: ExpectedIndentRange[], me settings: {}, folders: [{ name: 'foo', uri: 'test://foo' }] }; - const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService()); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFileFS()); const actual = await getFoldingRanges(languageModes, document, nRanges, null); let actualRanges = []; diff --git a/extensions/html-language-features/server/src/test/formatting.test.ts b/extensions/html-language-features/server/src/test/formatting.test.ts index 107001c7c007c..519ace5f9c6bb 100644 --- a/extensions/html-language-features/server/src/test/formatting.test.ts +++ b/extensions/html-language-features/server/src/test/formatting.test.ts @@ -10,7 +10,7 @@ import * as assert from 'assert'; import { getLanguageModes, TextDocument, Range, FormattingOptions, ClientCapabilities } from '../modes/languageModes'; import { format } from '../modes/formatting'; -import { getNodeFSRequestService } from '../node/nodeFs'; +import { getNodeFileFS } from '../node/nodeFs'; suite('HTML Embedded Formatting', () => { @@ -19,7 +19,7 @@ suite('HTML Embedded Formatting', () => { settings: options, folders: [{ name: 'foo', uri: 'test://foo' }] }; - const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService()); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFileFS()); let rangeStartOffset = value.indexOf('|'); let rangeEndOffset; diff --git a/extensions/html-language-features/server/src/test/rename.test.ts b/extensions/html-language-features/server/src/test/rename.test.ts index edebe68d7b261..b2aa23cc01681 100644 --- a/extensions/html-language-features/server/src/test/rename.test.ts +++ b/extensions/html-language-features/server/src/test/rename.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { WorkspaceEdit, TextDocument, getLanguageModes, ClientCapabilities } from '../modes/languageModes'; -import { getNodeFSRequestService } from '../node/nodeFs'; +import { getNodeFileFS } from '../node/nodeFs'; async function testRename(value: string, newName: string, expectedDocContent: string): Promise { @@ -17,7 +17,7 @@ async function testRename(value: string, newName: string, expectedDocContent: st settings: {}, folders: [{ name: 'foo', uri: 'test://foo' }] }; - const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService()); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFileFS()); const javascriptMode = languageModes.getMode('javascript') const position = document.positionAt(offset); @@ -49,7 +49,7 @@ async function testNoRename(value: string, newName: string): Promise { settings: {}, folders: [{ name: 'foo', uri: 'test://foo' }] }; - const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService()); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFileFS()); const javascriptMode = languageModes.getMode('javascript') const position = document.positionAt(offset); diff --git a/extensions/html-language-features/server/src/test/selectionRanges.test.ts b/extensions/html-language-features/server/src/test/selectionRanges.test.ts index c5166c7bb453d..afd286e3db5e2 100644 --- a/extensions/html-language-features/server/src/test/selectionRanges.test.ts +++ b/extensions/html-language-features/server/src/test/selectionRanges.test.ts @@ -7,7 +7,7 @@ import 'mocha'; import * as assert from 'assert'; import { getLanguageModes, ClientCapabilities, TextDocument, SelectionRange } from '../modes/languageModes'; import { getSelectionRanges } from '../modes/selectionRanges'; -import { getNodeFSRequestService } from '../node/nodeFs'; +import { getNodeFileFS } from '../node/nodeFs'; async function assertRanges(content: string, expected: (number | string)[][]): Promise { let message = `${content} gives selection range:\n`; @@ -19,7 +19,7 @@ async function assertRanges(content: string, expected: (number | string)[][]): P settings: {}, folders: [{ name: 'foo', uri: 'test://foo' }] }; - const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService()); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFileFS()); const document = TextDocument.create('test://foo.html', 'html', 1, content); const actualRanges = await getSelectionRanges(languageModes, document, [document.positionAt(offset)]); diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index 3cf242df18f15..2d7677d79a563 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -7,7 +7,7 @@ import 'mocha'; import * as assert from 'assert'; import { TextDocument, getLanguageModes, ClientCapabilities, Range, Position } from '../modes/languageModes'; import { newSemanticTokenProvider } from '../modes/semanticTokens'; -import { getNodeFSRequestService } from '../node/nodeFs'; +import { getNodeFileFS } from '../node/nodeFs'; interface ExpectedToken { startLine: number; @@ -22,7 +22,7 @@ async function assertTokens(lines: string[], expected: ExpectedToken[], ranges?: settings: {}, folders: [{ name: 'foo', uri: 'test://foo' }] }; - const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService()); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFileFS()); const semanticTokensProvider = newSemanticTokenProvider(languageModes); const legend = semanticTokensProvider.legend; diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index 20d1c9c9da4df..092377a677847 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -83,6 +83,11 @@ vscode-nls@^5.0.0: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-uri@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.2.tgz#ecfd1d066cb8ef4c3a208decdbab9a8c23d055d0" + integrity sha512-jkjy6pjU1fxUvI51P+gCsxg1u2n8LSt0W6KrCNQceaziKzff74GoWmjVG46KieVzybO1sttPQmYfrwSHey7GUA== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 595d893fdc1b73a0d2ac53fecca77c9316ed50fe Mon Sep 17 00:00:00 2001 From: MalikIdreesHasanKhan Date: Mon, 29 Nov 2021 02:32:14 -0800 Subject: [PATCH 0112/2210] Fixed typos. (#135729) --- src/vs/platform/update/common/update.ts | 2 +- src/vs/workbench/browser/web.main.ts | 2 +- .../services/workingCopy/common/untitledFileWorkingCopy.ts | 2 +- .../workingCopy/common/untitledFileWorkingCopyManager.ts | 2 +- src/vs/workbench/services/workingCopy/common/workingCopy.ts | 2 +- test/automation/src/debug.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index cf66654c330ee..ae027c2109ac8 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -29,7 +29,7 @@ export interface IUpdate { * * Available: There is an update available for download (linux). * Ready: Code will be updated as soon as it restarts (win32, darwin). - * Donwloaded: There is an update ready to be installed in the background (win32). + * Downloaded: There is an update ready to be installed in the background (win32). */ export const enum StateType { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index a0a9a1954c2ce..fdbd9256d6008 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -239,7 +239,7 @@ class BrowserMain extends Disposable { await userDataInitializationService.initializeRequiredResources(); // Important: Reload only local user configuration after initializing - // Reloading complete configuraiton blocks workbench until remote configuration is loaded. + // Reloading complete configuration blocks workbench until remote configuration is loaded. await configurationService.reloadLocalUserConfiguration(); mark('code/didInitRequiredUserData'); diff --git a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts index 4962d6e0ec6a5..597dc280d24a2 100644 --- a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts @@ -190,7 +190,7 @@ export class UntitledFileWorkingCopy ex this.setDirty(this.hasAssociatedFilePath || !!backup || Boolean(this.initialContents && this.initialContents.markDirty !== false)); // If we have initial contents, make sure to emit this - // as the appropiate events to the outside. + // as the appropriate events to the outside. if (!!backup || this.initialContents) { this._onDidChangeContent.fire(); } diff --git a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts index 4d1502c70cad5..c101e6a6ba940 100644 --- a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts @@ -152,7 +152,7 @@ export class UntitledFileWorkingCopyManager s.indexOf(text) >= 0); await this.code.dispatchKeybinding('enter'); await this.code.waitForElements(CONSOLE_EVALUATION_RESULT, false, From 9d24ab4a29db56076d3826cf88b3cd1d4fefe18e Mon Sep 17 00:00:00 2001 From: James Duley Date: Mon, 29 Nov 2021 23:56:22 +1300 Subject: [PATCH 0113/2210] Fix grammar in task configuration error message. (#137898) --- src/vs/workbench/contrib/tasks/common/taskConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 50ab002dd992b..a4f8b9f7bf4fc 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1393,7 +1393,7 @@ namespace ConfiguringTask { } let typeDeclaration = type ? TaskDefinitionRegistry.get(type) : undefined; if (!typeDeclaration) { - let message = nls.localize('ConfigurationParser.noTypeDefinition', 'Error: there is no registered task type \'{0}\'. Did you miss to install an extension that provides a corresponding task provider?', type); + let message = nls.localize('ConfigurationParser.noTypeDefinition', 'Error: there is no registered task type \'{0}\'. Did you miss installing an extension that provides a corresponding task provider?', type); context.problemReporter.error(message); return undefined; } From 9b525ea513f6775b8a20c01d0b70e1f0ebfb1fe8 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 12:23:20 +0100 Subject: [PATCH 0114/2210] Simplify `TextAreaWrapper` --- .../browser/controller/textAreaHandler.ts | 2 +- .../browser/controller/textAreaInput.ts | 48 +++++++++---------- .../test/browser/controller/imeTester.html | 1 - .../test/browser/controller/imeTester.ts | 3 +- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index cb7e0678e81be..aded618773153 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -226,7 +226,7 @@ export class TextAreaHandler extends ViewPart { } }; - const textAreaWrapper = this._register(new TextAreaWrapper(this.textArea)); + const textAreaWrapper = this._register(new TextAreaWrapper(this.textArea.domNode)); this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, textAreaWrapper)); this._register(this._textAreaInput.onKeyDown((e: IKeyboardEvent) => { diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index ab3ca2952e776..941207465915b 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -5,7 +5,6 @@ import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; -import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -700,18 +699,19 @@ class ClipboardEventUtils { export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrapper { - public readonly onKeyDown = this._register(dom.createEventEmitter(this._actual.domNode, 'keydown')).event; - public readonly onKeyUp = this._register(dom.createEventEmitter(this._actual.domNode, 'keyup')).event; - public readonly onCompositionStart = this._register(dom.createEventEmitter(this._actual.domNode, 'compositionstart')).event; - public readonly onCompositionUpdate = this._register(dom.createEventEmitter(this._actual.domNode, 'compositionupdate')).event; - public readonly onCompositionEnd = this._register(dom.createEventEmitter(this._actual.domNode, 'compositionend')).event; - public readonly onBeforeInput = this._register(dom.createEventEmitter(this._actual.domNode, 'beforeinput')).event; - public readonly onInput = >this._register(dom.createEventEmitter(this._actual.domNode, 'input')).event; - public readonly onCut = this._register(dom.createEventEmitter(this._actual.domNode, 'cut')).event; - public readonly onCopy = this._register(dom.createEventEmitter(this._actual.domNode, 'copy')).event; - public readonly onPaste = this._register(dom.createEventEmitter(this._actual.domNode, 'paste')).event; - public readonly onFocus = this._register(dom.createEventEmitter(this._actual.domNode, 'focus')).event; - public readonly onBlur = this._register(dom.createEventEmitter(this._actual.domNode, 'blur')).event; + public readonly onKeyDown = this._register(dom.createEventEmitter(this._actual, 'keydown')).event; + public readonly onKeyPress = this._register(dom.createEventEmitter(this._actual, 'keypress')).event; + public readonly onKeyUp = this._register(dom.createEventEmitter(this._actual, 'keyup')).event; + public readonly onCompositionStart = this._register(dom.createEventEmitter(this._actual, 'compositionstart')).event; + public readonly onCompositionUpdate = this._register(dom.createEventEmitter(this._actual, 'compositionupdate')).event; + public readonly onCompositionEnd = this._register(dom.createEventEmitter(this._actual, 'compositionend')).event; + public readonly onBeforeInput = this._register(dom.createEventEmitter(this._actual, 'beforeinput')).event; + public readonly onInput = >this._register(dom.createEventEmitter(this._actual, 'input')).event; + public readonly onCut = this._register(dom.createEventEmitter(this._actual, 'cut')).event; + public readonly onCopy = this._register(dom.createEventEmitter(this._actual, 'copy')).event; + public readonly onPaste = this._register(dom.createEventEmitter(this._actual, 'paste')).event; + public readonly onFocus = this._register(dom.createEventEmitter(this._actual, 'focus')).event; + public readonly onBlur = this._register(dom.createEventEmitter(this._actual, 'blur')).event; private _onSyntheticTap = this._register(new Emitter()); public readonly onSyntheticTap: Event = this._onSyntheticTap.event; @@ -719,20 +719,20 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap private _ignoreSelectionChangeTime: number; constructor( - private readonly _actual: FastDomNode + private readonly _actual: HTMLTextAreaElement ) { super(); this._ignoreSelectionChangeTime = 0; - this._register(dom.addDisposableListener(this._actual.domNode, TextAreaSyntethicEvents.Tap, () => this._onSyntheticTap.fire())); + this._register(dom.addDisposableListener(this._actual, TextAreaSyntethicEvents.Tap, () => this._onSyntheticTap.fire())); } public hasFocus(): boolean { - const shadowRoot = dom.getShadowRoot(this._actual.domNode); + const shadowRoot = dom.getShadowRoot(this._actual); if (shadowRoot) { - return shadowRoot.activeElement === this._actual.domNode; - } else if (dom.isInDOM(this._actual.domNode)) { - return document.activeElement === this._actual.domNode; + return shadowRoot.activeElement === this._actual; + } else if (dom.isInDOM(this._actual)) { + return document.activeElement === this._actual; } else { return false; } @@ -752,11 +752,11 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap public getValue(): string { // console.log('current value: ' + this._textArea.value); - return this._actual.domNode.value; + return this._actual.value; } public setValue(reason: string, value: string): void { - const textArea = this._actual.domNode; + const textArea = this._actual; if (textArea.value === value) { // No change return; @@ -767,15 +767,15 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap } public getSelectionStart(): number { - return this._actual.domNode.selectionDirection === 'backward' ? this._actual.domNode.selectionEnd : this._actual.domNode.selectionStart; + return this._actual.selectionDirection === 'backward' ? this._actual.selectionEnd : this._actual.selectionStart; } public getSelectionEnd(): number { - return this._actual.domNode.selectionDirection === 'backward' ? this._actual.domNode.selectionStart : this._actual.domNode.selectionEnd; + return this._actual.selectionDirection === 'backward' ? this._actual.selectionStart : this._actual.selectionEnd; } public setSelectionRange(reason: string, selectionStart: number, selectionEnd: number): void { - const textArea = this._actual.domNode; + const textArea = this._actual; let activeElement: Element | null = null; const shadowRoot = dom.getShadowRoot(textArea); diff --git a/src/vs/editor/test/browser/controller/imeTester.html b/src/vs/editor/test/browser/controller/imeTester.html index eff232fac474f..42adc4f56a57f 100644 --- a/src/vs/editor/test/browser/controller/imeTester.html +++ b/src/vs/editor/test/browser/controller/imeTester.html @@ -1,7 +1,6 @@ - + + + + + + + + diff --git a/src/vs/editor/test/browser/controller/imeRecorder.ts b/src/vs/editor/test/browser/controller/imeRecorder.ts new file mode 100644 index 0000000000000..c582b3ef44869 --- /dev/null +++ b/src/vs/editor/test/browser/controller/imeRecorder.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextAreaWrapper } from 'vs/editor/browser/controller/textAreaInput'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { IRecorded, IRecordedCompositionEvent, IRecordedEvent, IRecordedInputEvent, IRecordedKeyboardEvent, IRecordedTextareaState } from 'vs/editor/test/browser/controller/imeRecordedTypes'; +import * as browser from 'vs/base/browser/browser'; +import * as platform from 'vs/base/common/platform'; + +(() => { + + const startButton = document.getElementById('startRecording')!; + const endButton = document.getElementById('endRecording')!; + + let inputarea: HTMLTextAreaElement; + let disposables = new DisposableStore(); + let originTimeStamp = 0; + let recorded: IRecorded = { + env: null!, + initial: null!, + events: [], + final: null! + }; + + const readTextareaState = (): IRecordedTextareaState => { + return { + selectionDirection: inputarea.selectionDirection, + selectionEnd: inputarea.selectionEnd, + selectionStart: inputarea.selectionStart, + value: inputarea.value, + }; + }; + + startButton.onclick = () => { + disposables.clear(); + startTest(); + originTimeStamp = 0; + recorded = { + env: { + OS: platform.OS, + browser: { + isAndroid: browser.isAndroid, + isFirefox: browser.isFirefox, + isChrome: browser.isChrome, + isSafari: browser.isSafari + } + }, + initial: readTextareaState(), + events: [], + final: null! + }; + }; + endButton.onclick = () => { + recorded.final = readTextareaState(); + console.log(printRecordedData()); + }; + + function printRecordedData() { + const lines = []; + lines.push(`const recorded: IRecorded = {`); + lines.push(`\tenv: ${JSON.stringify(recorded.env)}, `); + lines.push(`\tinitial: ${printState(recorded.initial)}, `); + lines.push(`\tevents: [\n\t\t${recorded.events.map(ev => printEvent(ev)).join(',\n\t\t')}\n\t],`); + lines.push(`\tfinal: ${printState(recorded.final)},`); + lines.push(`}`); + + return lines.join('\n'); + + function printState(state: IRecordedTextareaState) { + return `{ value: '${state.value}', selectionStart: ${state.selectionStart}, selectionEnd: ${state.selectionEnd}, selectionDirection: '${state.selectionDirection}' }`; + } + function printEvent(ev: IRecordedEvent) { + if (ev.type === 'keydown' || ev.type === 'keypress' || ev.type === 'keyup') { + return `{ timeStamp: ${ev.timeStamp.toFixed(2)}, state: ${printState(ev.state)}, type: '${ev.type}', altKey: ${ev.altKey}, charCode: ${ev.charCode}, code: '${ev.code}', ctrlKey: ${ev.ctrlKey}, isComposing: ${ev.isComposing}, key: '${ev.key}', keyCode: ${ev.keyCode}, location: ${ev.location}, metaKey: ${ev.metaKey}, repeat: ${ev.repeat}, shiftKey: ${ev.shiftKey} }`; + } + if (ev.type === 'compositionstart' || ev.type === 'compositionupdate' || ev.type === 'compositionend') { + return `{ timeStamp: ${ev.timeStamp.toFixed(2)}, state: ${printState(ev.state)}, type: '${ev.type}', data: '${ev.data}' }`; + } + if (ev.type === 'beforeinput' || ev.type === 'input') { + return `{ timeStamp: ${ev.timeStamp.toFixed(2)}, state: ${printState(ev.state)}, type: '${ev.type}', data: ${ev.data === null ? 'null' : `'${ev.data}'`}, inputType: '${ev.inputType}', isComposing: ${ev.isComposing} }`; + } + return JSON.stringify(ev); + } + } + + function startTest() { + inputarea = document.createElement('textarea'); + document.body.appendChild(inputarea); + inputarea.focus(); + disposables.add(toDisposable(() => { + inputarea.remove(); + })); + const wrapper = disposables.add(new TextAreaWrapper(inputarea)); + + wrapper.setValue('', `aaaa`); + wrapper.setSelectionRange('', 2, 2); + + const recordEvent = (e: IRecordedEvent) => { + recorded.events.push(e); + }; + + const recordKeyboardEvent = (e: KeyboardEvent): void => { + if (e.type !== 'keydown' && e.type !== 'keypress' && e.type !== 'keyup') { + throw new Error(`Not supported!`); + } + if (originTimeStamp === 0) { + originTimeStamp = e.timeStamp; + } + const ev: IRecordedKeyboardEvent = { + timeStamp: e.timeStamp - originTimeStamp, + state: readTextareaState(), + type: e.type, + altKey: e.altKey, + charCode: e.charCode, + code: e.code, + ctrlKey: e.ctrlKey, + isComposing: e.isComposing, + key: e.key, + keyCode: e.keyCode, + location: e.location, + metaKey: e.metaKey, + repeat: e.repeat, + shiftKey: e.shiftKey + }; + recordEvent(ev); + }; + + const recordCompositionEvent = (e: CompositionEvent): void => { + if (e.type !== 'compositionstart' && e.type !== 'compositionupdate' && e.type !== 'compositionend') { + throw new Error(`Not supported!`); + } + if (originTimeStamp === 0) { + originTimeStamp = e.timeStamp; + } + const ev: IRecordedCompositionEvent = { + timeStamp: e.timeStamp - originTimeStamp, + state: readTextareaState(), + type: e.type, + data: e.data, + }; + recordEvent(ev); + }; + + const recordInputEvent = (e: InputEvent): void => { + if (e.type !== 'beforeinput' && e.type !== 'input') { + throw new Error(`Not supported!`); + } + if (originTimeStamp === 0) { + originTimeStamp = e.timeStamp; + } + const ev: IRecordedInputEvent = { + timeStamp: e.timeStamp - originTimeStamp, + state: readTextareaState(), + type: e.type, + data: e.data, + inputType: e.inputType, + isComposing: e.isComposing, + }; + recordEvent(ev); + }; + + wrapper.onKeyDown(recordKeyboardEvent); + wrapper.onKeyPress(recordKeyboardEvent); + wrapper.onKeyUp(recordKeyboardEvent); + wrapper.onCompositionStart(recordCompositionEvent); + wrapper.onCompositionUpdate(recordCompositionEvent); + wrapper.onCompositionEnd(recordCompositionEvent); + wrapper.onBeforeInput(recordInputEvent); + wrapper.onInput(recordInputEvent); + } + +})(); diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index b4ecc8c363c1d..763b9a56d0ff4 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -9,6 +9,8 @@ import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference } from 'vs/editor/common/model'; import * as dom from 'vs/base/browser/dom'; +import * as browser from 'vs/base/browser/browser'; +import * as platform from 'vs/base/common/platform'; // To run this test, open imeTester.html @@ -110,7 +112,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string } }; - let handler = new TextAreaInput(textAreaInputHost, new TextAreaWrapper(input)); + let handler = new TextAreaInput(textAreaInputHost, new TextAreaWrapper(input), platform.OS, browser); let output = document.createElement('pre'); output.className = 'output'; diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts new file mode 100644 index 0000000000000..fb51993718c81 --- /dev/null +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -0,0 +1,325 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { OperatingSystem } from 'vs/base/common/platform'; +import { ClipboardDataToCopy, ICompleteTextAreaWrapper, ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; +import { TextAreaState } from 'vs/editor/browser/controller/textAreaState'; +import { Position } from 'vs/editor/common/core/position'; +import { IRecorded, IRecordedEvent, IRecordedTextareaState } from 'vs/editor/test/browser/controller/imeRecordedTypes'; + +suite('TextAreaInput', () => { + + interface OutgoingType { + type: 'type'; + text: string; + replacePrevCharCnt: number; + replaceNextCharCnt: number; + positionDelta: number; + } + interface OutgoingCompositionStart { + type: 'compositionStart'; + revealDeltaColumns: number; + } + interface OutgoingCompositionUpdate { + type: 'compositionUpdate'; + data: string; + } + interface OutgoingCompositionEnd { + type: 'compositionEnd'; + } + type OutoingEvent = OutgoingType | OutgoingCompositionStart | OutgoingCompositionUpdate | OutgoingCompositionEnd; + + function yieldNow(): Promise { + return new Promise((resolve, reject) => { + queueMicrotask(resolve); + }); + } + + async function simulateInteraction(recorded: IRecorded): Promise { + let disposables = new DisposableStore(); + const host: ITextAreaInputHost = { + getDataToCopy: function (html: boolean): ClipboardDataToCopy { + throw new Error('Function not implemented.'); + }, + getScreenReaderContent: function (currentState: TextAreaState): TextAreaState { + return new TextAreaState( + recorded.initial.value, + recorded.initial.selectionStart, + recorded.initial.selectionEnd, + null, + null + ); + // return TextAreaState.selectedText(''); + }, + deduceModelPosition: function (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position { + throw new Error('Function not implemented.'); + } + }; + const wrapper = disposables.add(new class extends Disposable implements ICompleteTextAreaWrapper { + private _onKeyDown = this._register(new Emitter()); + readonly onKeyDown = this._onKeyDown.event; + + private _onKeyPress = this._register(new Emitter()); + readonly onKeyPress = this._onKeyPress.event; + + private _onKeyUp = this._register(new Emitter()); + readonly onKeyUp = this._onKeyUp.event; + + private _onCompositionStart = this._register(new Emitter()); + readonly onCompositionStart = this._onCompositionStart.event; + + private _onCompositionUpdate = this._register(new Emitter()); + readonly onCompositionUpdate = this._onCompositionUpdate.event; + + private _onCompositionEnd = this._register(new Emitter()); + readonly onCompositionEnd = this._onCompositionEnd.event; + + private _onBeforeInput = this._register(new Emitter()); + readonly onBeforeInput = this._onBeforeInput.event; + + private _onInput = this._register(new Emitter()); + readonly onInput = this._onInput.event; + + readonly onCut = Event.None; + readonly onCopy = Event.None; + readonly onPaste = Event.None; + + readonly _onFocus = this._register(new Emitter()); + readonly onFocus = this._onFocus.event; + + readonly onBlur = Event.None; + readonly onSyntheticTap = Event.None; + + private _state: IRecordedTextareaState; + + constructor() { + super(); + this._state = { + selectionDirection: 'none', + selectionEnd: 0, + selectionStart: 0, + value: '' + }; + } + + public dispatchRecordedEvent(event: IRecordedEvent): void { + this._state.value = event.state.value; + this._state.selectionStart = event.state.selectionStart; + this._state.selectionEnd = event.state.selectionEnd; + this._state.selectionDirection = event.state.selectionDirection; + + if (event.type === 'keydown' || event.type === 'keypress' || event.type === 'keyup') { + const mockEvent = { + timeStamp: event.timeStamp, + type: event.type, + altKey: event.altKey, + charCode: event.charCode, + code: event.code, + ctrlKey: event.ctrlKey, + isComposing: event.isComposing, + key: event.key, + keyCode: event.keyCode, + location: event.location, + metaKey: event.metaKey, + repeat: event.repeat, + shiftKey: event.shiftKey, + }; + if (event.type === 'keydown') { + this._onKeyDown.fire(mockEvent); + } else if (event.type === 'keypress') { + this._onKeyPress.fire(mockEvent); + } else { + this._onKeyUp.fire(mockEvent); + } + } else if (event.type === 'compositionstart' || event.type === 'compositionupdate' || event.type === 'compositionend') { + const mockEvent = { + timeStamp: event.timeStamp, + type: event.type, + data: event.data + }; + if (event.type === 'compositionstart') { + this._onCompositionStart.fire(mockEvent); + } else if (event.type === 'compositionupdate') { + this._onCompositionUpdate.fire(mockEvent); + } else { + this._onCompositionEnd.fire(mockEvent); + } + } else if (event.type === 'beforeinput' || event.type === 'input') { + const mockEvent = { + timeStamp: event.timeStamp, + type: event.type, + data: event.data, + inputType: event.inputType, + isComposing: event.isComposing, + }; + if (event.type === 'beforeinput') { + this._onBeforeInput.fire(mockEvent); + } else { + this._onInput.fire(mockEvent); + } + } else { + throw new Error(`Not Implemented`); + } + } + + getValue(): string { + return this._state.value; + } + setValue(reason: string, value: string): void { + this._state.value = value; + } + getSelectionStart(): number { + return this._state.selectionDirection === 'backward' ? this._state.selectionEnd : this._state.selectionStart; + } + getSelectionEnd(): number { + return this._state.selectionDirection === 'backward' ? this._state.selectionStart : this._state.selectionEnd; + } + setSelectionRange(reason: string, selectionStart: number, selectionEnd: number): void { + this._state.selectionStart = selectionStart; + this._state.selectionEnd = selectionEnd; + this._state.selectionDirection = (selectionStart !== selectionEnd ? 'forward' : 'none'); + } + + public setIgnoreSelectionChangeTime(reason: string): void { } + public getIgnoreSelectionChangeTime(): number { return Date.now(); } + public resetSelectionChangeTime(): void { } + + public hasFocus(): boolean { return true; } + }); + const input = disposables.add(new TextAreaInput(host, wrapper, recorded.env.OS, recorded.env.browser)); + + wrapper._onFocus.fire(null as any); + + let outgoingEvents: OutoingEvent[] = []; + + disposables.add(input.onType((e) => outgoingEvents.push({ + type: 'type', + text: e.text, + replacePrevCharCnt: e.replacePrevCharCnt, + replaceNextCharCnt: e.replaceNextCharCnt, + positionDelta: e.positionDelta, + }))); + disposables.add(input.onCompositionStart((e) => outgoingEvents.push({ + type: 'compositionStart', + revealDeltaColumns: e.revealDeltaColumns, + }))); + disposables.add(input.onCompositionUpdate((e) => outgoingEvents.push({ + type: 'compositionUpdate', + data: e.data, + }))); + disposables.add(input.onCompositionEnd((e) => outgoingEvents.push({ + type: 'compositionEnd' + }))); + + for (const event of recorded.events) { + wrapper.dispatchRecordedEvent(event); + await yieldNow(); + } + + return outgoingEvents; + } + + function interpretTypeEvents(initialState: IRecordedTextareaState, events: OutoingEvent[]): IRecordedTextareaState { + let text = initialState.value; + let selectionStart = initialState.selectionStart; + let selectionEnd = initialState.selectionEnd; + for (const event of events) { + if (event.type === 'type') { + text = ( + text.substring(0, selectionStart - event.replacePrevCharCnt) + + event.text + + text.substring(selectionEnd + event.replaceNextCharCnt) + ); + selectionStart = selectionStart - event.replacePrevCharCnt + event.text.length; + selectionEnd = selectionStart; + + if (event.positionDelta) { + selectionStart += event.positionDelta; + selectionEnd += event.positionDelta; + } + } + } + return { + value: text, + selectionStart: selectionStart, + selectionEnd: selectionEnd, + selectionDirection: 'none' + }; + } + + test('macOS - chrome - Korean test', async () => { + // macOS, 2-Set Korean, type 'dkrk' and click + const recorded: IRecorded = { + env: { + OS: OperatingSystem.Macintosh, + browser: { + isAndroid: false, + isFirefox: false, + isChrome: true, + isSafari: false + } + }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: false, key: 'ㅇ', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 6.20, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionstart', data: '' }, + { timeStamp: 6.40, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'beforeinput', data: 'ㅇ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 6.50, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionupdate', data: 'ㅇ' }, + { timeStamp: 6.90, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'ㅇ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 136.10, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: true, key: 'ㅇ', keyCode: 68, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 288.10, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'ㅏ', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 296.00, state: { value: 'aaㅇaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 296.00, state: { value: 'aaㅇaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: '아' }, + { timeStamp: 296.40, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 368.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'ㅏ', keyCode: 75, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 536.10, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'ㄱ', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 543.20, state: { value: 'aa아aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: '악', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 543.30, state: { value: 'aa아aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: '악' }, + { timeStamp: 543.60, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: '악', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 632.00, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'ㄱ', keyCode: 82, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 783.90, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'ㅏ', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 790.70, state: { value: 'aa악aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 790.80, state: { value: 'aa악aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: '아' }, + { timeStamp: 791.20, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 791.20, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionend', data: '아' }, + { timeStamp: 791.30, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionstart', data: '' }, + { timeStamp: 791.30, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: '가', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 791.30, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: '가' }, + { timeStamp: 791.50, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'input', data: '가', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 880.10, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'ㅏ', keyCode: 75, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2209.00, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'compositionend', data: '가' } + ], + final: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + + assert.deepStrictEqual(actualOutgoingEvents, ([ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㅇ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㅇ' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '아' }, + { type: 'type', text: '악', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '악' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '아' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: '가', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '가' }, + { type: 'type', text: '가', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ])); + + const actualResultingState = interpretTypeEvents(recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + +}); From 322aa4f631c14b59af97ae2d0b9e2b61ea8777ea Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 15:24:24 +0100 Subject: [PATCH 0125/2210] Add more tests for `TextAreaInput` --- .../browser/controller/textAreaInput.test.ts | 137 ++++++++++++++++-- 1 file changed, 125 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index fb51993718c81..6679916719c07 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -255,15 +255,7 @@ suite('TextAreaInput', () => { test('macOS - chrome - Korean test', async () => { // macOS, 2-Set Korean, type 'dkrk' and click const recorded: IRecorded = { - env: { - OS: OperatingSystem.Macintosh, - browser: { - isAndroid: false, - isFirefox: false, - isChrome: true, - isSafari: false - } - }, + env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, events: [ { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: false, key: 'ㅇ', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, @@ -298,8 +290,7 @@ suite('TextAreaInput', () => { }; const actualOutgoingEvents = await simulateInteraction(recorded); - - assert.deepStrictEqual(actualOutgoingEvents, ([ + assert.deepStrictEqual(actualOutgoingEvents, [ { type: 'compositionStart', revealDeltaColumns: 0 }, { type: 'type', text: 'ㅇ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, { type: 'compositionUpdate', data: 'ㅇ' }, @@ -316,10 +307,132 @@ suite('TextAreaInput', () => { { type: 'compositionUpdate', data: '가' }, { type: 'type', text: '가', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, { type: 'compositionEnd' } - ])); + ]); + + const actualResultingState = interpretTypeEvents(recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + + test('macOS - chrome - Japanese using Hiragana (Google)', async () => { + // macOS, Hiragana (Google), type 'sennsei' and Enter + const recorded: IRecorded = { + env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: false, key: 's', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 8.50, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionstart', data: '' }, + { timeStamp: 8.70, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'beforeinput', data: 's', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 8.70, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionupdate', data: 's' }, + { timeStamp: 9.30, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 's', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 111.70, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 's', keyCode: 83, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 439.80, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'e', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 444.50, state: { value: 'aasaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: 'せ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 444.60, state: { value: 'aasaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: 'せ' }, + { timeStamp: 445.20, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'せ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 559.90, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1943.90, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1949.30, state: { value: 'aaせaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: 'せn', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1949.40, state: { value: 'aaせaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: 'せn' }, + { timeStamp: 1949.90, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'input', data: 'せn', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2039.90, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 78, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2207.80, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2215.70, state: { value: 'aaせnaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'none' }, type: 'beforeinput', data: 'せん', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2215.80, state: { value: 'aaせnaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'none' }, type: 'compositionupdate', data: 'せん' }, + { timeStamp: 2216.10, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'input', data: 'せん', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2311.90, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 78, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2551.90, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 's', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2557.00, state: { value: 'aaせんaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'none' }, type: 'beforeinput', data: 'せんs', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2557.00, state: { value: 'aaせんaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'none' }, type: 'compositionupdate', data: 'せんs' }, + { timeStamp: 2557.40, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'none' }, type: 'input', data: 'せんs', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2671.70, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 's', keyCode: 83, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2903.80, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'e', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2912.30, state: { value: 'aaせんsaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'none' }, type: 'beforeinput', data: 'せんせ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2912.50, state: { value: 'aaせんsaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'none' }, type: 'compositionupdate', data: 'せんせ' }, + { timeStamp: 2912.90, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'none' }, type: 'input', data: 'せんせ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3023.90, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3519.90, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'i', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3537.10, state: { value: 'aaせんせaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'none' }, type: 'beforeinput', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3537.10, state: { value: 'aaせんせaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'none' }, type: 'compositionupdate', data: 'せんせい' }, + { timeStamp: 3537.60, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'none' }, type: 'input', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3639.90, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'i', keyCode: 73, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 4887.80, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: true, key: 'Enter', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 4892.80, state: { value: 'aaせんせいaa', selectionStart: 2, selectionEnd: 6, selectionDirection: 'none' }, type: 'beforeinput', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 4892.90, state: { value: 'aaせんせいaa', selectionStart: 2, selectionEnd: 6, selectionDirection: 'none' }, type: 'compositionupdate', data: 'せんせい' }, + { timeStamp: 4893.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'none' }, type: 'input', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 4893.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'none' }, type: 'compositionend', data: 'せんせい' }, + { timeStamp: 4967.80, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Enter', keyCode: 13, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'none' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 's', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 's' }, + { type: 'type', text: 'せ', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せ' }, + { type: 'type', text: 'せn', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せn' }, + { type: 'type', text: 'せん', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せん' }, + { type: 'type', text: 'せんs', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんs' }, + { type: 'type', text: 'せんせ', replacePrevCharCnt: 3, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせ' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 3, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせい' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 4, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせい' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 4, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); const actualResultingState = interpretTypeEvents(recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); + test('macOS - chrome - Chinese using Pinyin - Traditional', async () => { + // macOS, Pinyin - Traditional, type 'xu' and '1' + const recorded: IRecorded = { + env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyX', ctrlKey: false, isComposing: false, key: 'x', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 48.70, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionstart', data: '' }, + { timeStamp: 48.80, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'beforeinput', data: 'x', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 48.90, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionupdate', data: 'x' }, + { timeStamp: 49.20, state: { value: 'aaxaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'x', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 127.80, state: { value: 'aaxaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyX', ctrlKey: false, isComposing: true, key: 'x', keyCode: 88, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 480.00, state: { value: 'aaxaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyU', ctrlKey: false, isComposing: true, key: 'u', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 535.60, state: { value: 'aaxaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: 'xu', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 535.70, state: { value: 'aaxaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: 'xu' }, + { timeStamp: 535.90, state: { value: 'aaxuaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'input', data: 'xu', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 575.80, state: { value: 'aaxuaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyU', ctrlKey: false, isComposing: true, key: 'u', keyCode: 85, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1055.90, state: { value: 'aaxuaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'Digit1', ctrlKey: false, isComposing: true, key: '1', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1061.70, state: { value: 'aaxuaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'none' }, type: 'beforeinput', data: '需', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1061.80, state: { value: 'aaxuaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'none' }, type: 'compositionupdate', data: '需' }, + { timeStamp: 1063.20, state: { value: 'aa需aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: '需', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1063.30, state: { value: 'aa需aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionend', data: '需' }, + { timeStamp: 1207.90, state: { value: 'aa需aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'Digit1', ctrlKey: false, isComposing: false, key: '1', keyCode: 49, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { value: 'aa需aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'x', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'x' }, + { type: 'type', text: 'xu', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'xu' }, + { type: 'type', text: '需', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '需' }, + { type: 'type', text: '需', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); + + const actualResultingState = interpretTypeEvents(recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); }); From 87b3f754afc2ef8c30ad6c36a1ed398fa51243d0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 29 Nov 2021 15:35:45 +0100 Subject: [PATCH 0126/2210] perf improvements - do not read cache if it does not exist - update cache only once eventually - revalidate cache eventually --- .../configuration/browser/configuration.ts | 20 +++++++++++-------- .../browser/configurationService.ts | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 5d539261a4fd6..79e65587d59f8 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -30,6 +30,8 @@ import { isObject } from 'vs/base/common/types'; export class DefaultConfiguration extends Disposable { + private static DEFAULT_OVERRIDES_CACHE_EXISTS_KEY = 'DefaultOverridesCacheExists'; + private readonly configurationRegistry = Registry.as(Extensions.Configuration); private cachedConfigurationDefaultsOverrides: IStringDictionary = {}; private readonly cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; @@ -58,7 +60,7 @@ export class DefaultConfiguration extends Disposable { async initialize(): Promise { await this.initializeCachedConfigurationDefaultsOverrides(); this._configurationModel = undefined; - this._register(this.configurationRegistry.onDidUpdateConfiguration(({ defaultsOverrides }) => this.onDidUpdateConfiguration(defaultsOverrides))); + this._register(this.configurationRegistry.onDidUpdateConfiguration(() => this.onDidUpdateConfiguration())); return this.configurationModel; } @@ -74,9 +76,12 @@ export class DefaultConfiguration extends Disposable { if (!this.initiaizeCachedConfigurationDefaultsOverridesPromise) { this.initiaizeCachedConfigurationDefaultsOverridesPromise = (async () => { try { - const content = await this.configurationCache.read(this.cacheKey); - if (content) { - this.cachedConfigurationDefaultsOverrides = JSON.parse(content); + // Read only when the cache exists + if (window.localStorage.getItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY)) { + const content = await this.configurationCache.read(this.cacheKey); + if (content) { + this.cachedConfigurationDefaultsOverrides = JSON.parse(content); + } } } catch (error) { /* ignore */ } this.cachedConfigurationDefaultsOverrides = isObject(this.cachedConfigurationDefaultsOverrides) ? this.cachedConfigurationDefaultsOverrides : {}; @@ -85,12 +90,9 @@ export class DefaultConfiguration extends Disposable { return this.initiaizeCachedConfigurationDefaultsOverridesPromise; } - private onDidUpdateConfiguration(defaultsOverrides?: boolean): void { + private onDidUpdateConfiguration(): void { this._configurationModel = undefined; this._onDidChangeConfiguration.fire(this.configurationModel); - if (defaultsOverrides) { - this.updateCachedConfigurationDefaultsOverrides(); - } } private async updateCachedConfigurationDefaultsOverrides(): Promise { @@ -103,8 +105,10 @@ export class DefaultConfiguration extends Disposable { } try { if (Object.keys(cachedConfigurationDefaultsOverrides).length) { + window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await this.configurationCache.write(this.cacheKey, JSON.stringify(cachedConfigurationDefaultsOverrides)); } else { + window.localStorage.removeItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY); await this.configurationCache.remove(this.cacheKey); } } catch (error) {/* Ignore error */ } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 2dca97e623d18..9e8880d70576f 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -1159,5 +1159,5 @@ class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenc const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); -workbenchContributionsRegistry.registerWorkbenchContribution(ResetConfigurationDefaultsOverridesCache, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution(ResetConfigurationDefaultsOverridesCache, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(UpdateExperimentalSettingsDefaults, LifecyclePhase.Restored); From b6786d86ec215a6663ba4f47361595e787c7ed1f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 29 Nov 2021 15:58:08 +0100 Subject: [PATCH 0127/2210] fix tests --- .../configuration/browser/configuration.ts | 15 ++++++++++++--- .../test/browser/configuration.test.ts | 8 ++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 79e65587d59f8..bc9ad5c1fdff5 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -30,7 +30,7 @@ import { isObject } from 'vs/base/common/types'; export class DefaultConfiguration extends Disposable { - private static DEFAULT_OVERRIDES_CACHE_EXISTS_KEY = 'DefaultOverridesCacheExists'; + static readonly DEFAULT_OVERRIDES_CACHE_EXISTS_KEY = 'DefaultOverridesCacheExists'; private readonly configurationRegistry = Registry.as(Extensions.Configuration); private cachedConfigurationDefaultsOverrides: IStringDictionary = {}; @@ -39,6 +39,8 @@ export class DefaultConfiguration extends Disposable { private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + private updateCache: boolean = false; + constructor( private readonly configurationCache: IConfigurationCache, environmentService: IWorkbenchEnvironmentService, @@ -60,11 +62,12 @@ export class DefaultConfiguration extends Disposable { async initialize(): Promise { await this.initializeCachedConfigurationDefaultsOverrides(); this._configurationModel = undefined; - this._register(this.configurationRegistry.onDidUpdateConfiguration(() => this.onDidUpdateConfiguration())); + this._register(this.configurationRegistry.onDidUpdateConfiguration(({ defaultsOverrides }) => this.onDidUpdateConfiguration(defaultsOverrides))); return this.configurationModel; } reload(): ConfigurationModel { + this.updateCache = true; this.cachedConfigurationDefaultsOverrides = {}; this._configurationModel = undefined; this.updateCachedConfigurationDefaultsOverrides(); @@ -90,12 +93,18 @@ export class DefaultConfiguration extends Disposable { return this.initiaizeCachedConfigurationDefaultsOverridesPromise; } - private onDidUpdateConfiguration(): void { + private onDidUpdateConfiguration(defaultsOverrides?: boolean): void { this._configurationModel = undefined; this._onDidChangeConfiguration.fire(this.configurationModel); + if (defaultsOverrides) { + this.updateCachedConfigurationDefaultsOverrides(); + } } private async updateCachedConfigurationDefaultsOverrides(): Promise { + if (!this.updateCache) { + return; + } const cachedConfigurationDefaultsOverrides: IStringDictionary = {}; const configurationDefaultsOverrides = this.configurationRegistry.getConfigurationDefaultsOverrides(); for (const [key, value] of configurationDefaultsOverrides) { diff --git a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts index 070094ecc0e82..b8011291bc6c2 100644 --- a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts @@ -55,6 +55,7 @@ suite('DefaultConfiguration', () => { }); test('configuration default overrides are read from cache', async () => { + window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); @@ -64,6 +65,7 @@ suite('DefaultConfiguration', () => { }); test('configuration default overrides are read from cache when model is read before initialize', async () => { + window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); @@ -76,6 +78,7 @@ suite('DefaultConfiguration', () => { }); test('configuration default overrides are read from cache', async () => { + window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); @@ -86,6 +89,7 @@ suite('DefaultConfiguration', () => { test('configuration default overrides read from cache override environment', async () => { const environmentService = new BrowserWorkbenchEnvironmentService({ logsPath: joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'logs'), workspaceId: '', configurationDefaults: { 'test.configurationDefaultsOverride': 'envOverrideValue' } }, TestProductService); + window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); const testObject = new DefaultConfiguration(configurationCache, environmentService); @@ -95,6 +99,7 @@ suite('DefaultConfiguration', () => { }); test('configuration default overrides are read from cache when default configuration changed', async () => { + window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); await testObject.initialize(); @@ -116,6 +121,7 @@ suite('DefaultConfiguration', () => { }); test('configuration default overrides are not read from cache after reload', async () => { + window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); @@ -126,6 +132,7 @@ suite('DefaultConfiguration', () => { }); test('cache is reset after reload', async () => { + window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); @@ -138,6 +145,7 @@ suite('DefaultConfiguration', () => { test('configuration default overrides are written in cache', async () => { const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); await testObject.initialize(); + testObject.reload(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); configurationRegistry.registerDefaultConfigurations([{ overrides: { 'test.configurationDefaultsOverride': 'newoverrideValue' } }]); await promise; From 0c48cf395ffb19cc3cdb323a77b540f74d3ad7ed Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 29 Nov 2021 16:03:41 +0100 Subject: [PATCH 0128/2210] Fix handling of notebook links in untitled files --- .../view/renderers/backLayerWebView.ts | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 5978173961302..4625aac3e8c7a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -131,6 +131,7 @@ export class BackLayerWebView extends Disposable { @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IConfigurationService private readonly configurationService: IConfigurationService, @IModeService private readonly modeService: IModeService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, ) { super(); @@ -618,11 +619,37 @@ var requirejs = (function() { } case 'clicked-link': { + let linkToOpen: URI | string | undefined; if (matchesSomeScheme(data.href, Schemas.http, Schemas.https, Schemas.mailto)) { - this.openerService.open(data.href, { fromUserGesture: true }); + linkToOpen = data.href; } else if (!/^[\w\-]+:/.test(data.href)) { - const path = URI.joinPath(dirname(this.documentUri), data.href); - this.openerService.open(path, { fromUserGesture: true }); + if (this.documentUri.scheme === Schemas.untitled) { + const folders = this.workspaceContextService.getWorkspace().folders; + if (!folders.length) { + return; + } + linkToOpen = URI.joinPath(folders[0].uri, data.href); + } else { + if (data.href.startsWith('/')) { + // Resolve relative to workspace + let folder = this.workspaceContextService.getWorkspaceFolder(this.documentUri); + if (!folder) { + const folders = this.workspaceContextService.getWorkspace().folders; + if (!folders.length) { + return; + } + folder = folders[0]; + } + linkToOpen = URI.joinPath(folder.uri, data.href); + } else { + // Resolve relative to notebook document + linkToOpen = URI.joinPath(dirname(this.documentUri), data.href); + } + } + } + + if (linkToOpen) { + this.openerService.open(linkToOpen, { fromUserGesture: true }); } break; } From 5babe412c5c625d671c277628f8b894e879cc097 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 16:08:38 +0100 Subject: [PATCH 0129/2210] Add more tests for `TextAreaInput` --- .../browser/controller/textAreaInput.test.ts | 174 +++++++++++++++++- 1 file changed, 165 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index 6679916719c07..4db1fb3692087 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { OperatingSystem } from 'vs/base/common/platform'; -import { ClipboardDataToCopy, ICompleteTextAreaWrapper, ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; +import { ClipboardDataToCopy, IBrowser, ICompleteTextAreaWrapper, ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; import { TextAreaState } from 'vs/editor/browser/controller/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { IRecorded, IRecordedEvent, IRecordedTextareaState } from 'vs/editor/test/browser/controller/imeRecordedTypes'; @@ -224,7 +224,7 @@ suite('TextAreaInput', () => { return outgoingEvents; } - function interpretTypeEvents(initialState: IRecordedTextareaState, events: OutoingEvent[]): IRecordedTextareaState { + function interpretTypeEvents(browser: IBrowser, initialState: IRecordedTextareaState, events: OutoingEvent[]): IRecordedTextareaState { let text = initialState.value; let selectionStart = initialState.selectionStart; let selectionEnd = initialState.selectionEnd; @@ -248,11 +248,11 @@ suite('TextAreaInput', () => { value: text, selectionStart: selectionStart, selectionEnd: selectionEnd, - selectionDirection: 'none' + selectionDirection: browser.isFirefox ? 'forward' : 'none' }; } - test('macOS - chrome - Korean test', async () => { + test('macOS - Chrome - Korean using 2-Set Korean (1)', async () => { // macOS, 2-Set Korean, type 'dkrk' and click const recorded: IRecorded = { env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, @@ -309,11 +309,56 @@ suite('TextAreaInput', () => { { type: 'compositionEnd' } ]); - const actualResultingState = interpretTypeEvents(recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); - test('macOS - chrome - Japanese using Hiragana (Google)', async () => { + test('macOS - Chrome - Korean using 2-Set Korean (2)', async () => { + // macOS, 2-Set Korean, type 'qud' and click + // See https://github.com/microsoft/vscode/issues/134254 + const recorded: IRecorded = { + env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyQ', ctrlKey: false, isComposing: false, key: 'ㅂ', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 7.40, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionstart', data: '' }, + { timeStamp: 7.60, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'beforeinput', data: 'ㅂ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 7.60, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionupdate', data: 'ㅂ' }, + { timeStamp: 8.20, state: { value: 'aaㅂaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'ㅂ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 136.10, state: { value: 'aaㅂaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyQ', ctrlKey: false, isComposing: true, key: 'ㅂ', keyCode: 81, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 680.10, state: { value: 'aaㅂaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyU', ctrlKey: false, isComposing: true, key: 'ㅕ', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 687.20, state: { value: 'aaㅂaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: '벼', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 687.40, state: { value: 'aaㅂaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: '벼' }, + { timeStamp: 688.80, state: { value: 'aa벼aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: '벼', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 768.10, state: { value: 'aa벼aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyU', ctrlKey: false, isComposing: true, key: 'ㅕ', keyCode: 85, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1768.00, state: { value: 'aa벼aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: true, key: 'ㅇ', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1775.00, state: { value: 'aa벼aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: '병', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1775.10, state: { value: 'aa벼aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: '병' }, + { timeStamp: 1775.60, state: { value: 'aa병aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: '병', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1928.10, state: { value: 'aa병aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: true, key: 'ㅇ', keyCode: 68, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 6565.70, state: { value: 'aa병aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionend', data: '병' } + ], + final: { value: 'aa병aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㅂ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㅂ' }, + { type: 'type', text: '벼', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '벼' }, + { type: 'type', text: '병', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '병' }, + { type: 'type', text: '병', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + + test('macOS - Chrome - Japanese using Hiragana (Google)', async () => { // macOS, Hiragana (Google), type 'sennsei' and Enter const recorded: IRecorded = { env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, @@ -388,11 +433,11 @@ suite('TextAreaInput', () => { { type: 'compositionEnd' } ]); - const actualResultingState = interpretTypeEvents(recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); - test('macOS - chrome - Chinese using Pinyin - Traditional', async () => { + test('macOS - Chrome - Chinese using Pinyin - Traditional', async () => { // macOS, Pinyin - Traditional, type 'xu' and '1' const recorded: IRecorded = { env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, @@ -432,7 +477,118 @@ suite('TextAreaInput', () => { { type: 'compositionEnd' } ]); - const actualResultingState = interpretTypeEvents(recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + + test('macOS - Chrome - long press with arrow keys', async () => { + // macOS, English, long press o, press arrow right twice and then press Enter + // See https://github.com/microsoft/vscode/issues/67739 + const recorded: IRecorded = { + env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: false, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'keypress', altKey: false, charCode: 111, code: 'KeyO', ctrlKey: false, isComposing: false, key: 'o', keyCode: 111, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2.80, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'beforeinput', data: 'o', inputType: 'insertText', isComposing: false }, + { timeStamp: 3.40, state: { value: 'aaoaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'o', inputType: 'insertText', isComposing: false }, + { timeStamp: 500.50, state: { value: 'aaoaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: false, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 583.90, state: { value: 'aaoaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: false, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 667.60, state: { value: 'aaoaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: false, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 750.90, state: { value: 'aaoaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: false, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 835.00, state: { value: 'aaoaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: false, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 856.10, state: { value: 'aaoaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: false, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1952.10, state: { value: 'aaoaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'ArrowRight', ctrlKey: false, isComposing: false, key: 'ArrowRight', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1956.50, state: { value: 'aaoaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionstart', data: 'o' }, + { timeStamp: 1956.80, state: { value: 'aaoaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: 'ô', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1956.90, state: { value: 'aaoaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: 'ô' }, + { timeStamp: 1960.60, state: { value: 'aaôaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'ô', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2088.10, state: { value: 'aaôaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'ArrowRight', ctrlKey: false, isComposing: true, key: 'ArrowRight', keyCode: 39, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2480.10, state: { value: 'aaôaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'ArrowRight', ctrlKey: false, isComposing: true, key: 'ArrowRight', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2484.30, state: { value: 'aaôaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: 'ö', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2484.40, state: { value: 'aaôaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: 'ö' }, + { timeStamp: 2484.70, state: { value: 'aaöaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'ö', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2584.20, state: { value: 'aaöaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'ArrowRight', ctrlKey: false, isComposing: true, key: 'ArrowRight', keyCode: 39, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 6424.20, state: { value: 'aaöaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: true, key: 'Enter', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 6431.70, state: { value: 'aaöaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: 'ö', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 6431.70, state: { value: 'aaöaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionupdate', data: 'ö' }, + { timeStamp: 6431.80, state: { value: 'aaöaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'ö', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 6431.90, state: { value: 'aaöaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionend', data: 'ö' }, + { timeStamp: 6496.20, state: { value: 'aaöaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Enter', keyCode: 13, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { value: 'aaöaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, + }; + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'type', text: 'o', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionStart', revealDeltaColumns: -1 }, + { type: 'type', text: 'ô', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ô' }, + { type: 'type', text: 'ö', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ö' }, + { type: 'type', text: 'ö', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ö' }, + { type: 'type', text: 'ö', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + + test('macOS - Firefox - long press with mouse', async () => { + // macOS, English, long press e and choose using mouse + // See https://github.com/microsoft/monaco-editor/issues/2358 + const recorded: IRecorded = { + env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: true, isChrome: false, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keypress', altKey: false, charCode: 101, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 101, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 7.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 'e', inputType: 'insertText', isComposing: false }, + { timeStamp: 7.00, state: { value: 'aaeaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'e', inputType: 'insertText', isComposing: false }, + { timeStamp: 500.00, state: { value: 'aaeaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 667.00, state: { value: 'aaeaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 750.00, state: { value: 'aaeaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 834.00, state: { value: 'aaeaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 917.00, state: { value: 'aaeaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 1001.00, state: { value: 'aaeaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: true, shiftKey: false }, + { timeStamp: 1024.00, state: { value: 'aaeaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: false, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2988.00, state: { value: 'aaeaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'è', inputType: 'insertText', isComposing: false }, + { timeStamp: 2988.00, state: { value: 'aaèaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'è', inputType: 'insertText', isComposing: false } + ], + final: { value: 'aaèaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'type', text: 'e', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'type', text: 'è', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + + test('macOS - Firefox - inserting emojis', async () => { + // macOS, English, from the edit menu, click Emoji & Symbols, select an emoji + // See https://github.com/microsoft/vscode/issues/106392 + const recorded: IRecorded = { + env: { OS: OperatingSystem.Macintosh, browser: { isAndroid: false, isFirefox: true, isChrome: false, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: '😍', inputType: 'insertText', isComposing: false }, + { timeStamp: 1.00, state: { value: 'aa😍aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '😍', inputType: 'insertText', isComposing: false } + ], + final: { value: 'aa😍aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'type', text: '😍', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); }); From 732e263ee9223f015ffbde178c2c2a626c0c5266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 29 Nov 2021 16:30:08 +0100 Subject: [PATCH 0130/2210] web: allow embedded to add commands to menus --- src/vs/workbench/workbench.web.api.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index d619ce200892c..0185fdacf19a6 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -20,6 +20,7 @@ import { ICredentialsProvider } from 'vs/workbench/services/credentials/common/c import { TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { DeferredPromise } from 'vs/base/common/async'; +import { asArray } from 'vs/base/common/arrays'; interface IResourceUriProvider { (uri: URI): URI; @@ -138,6 +139,18 @@ interface IShowPortCandidate { (host: string, port: number, detail: string): Promise; } +enum Menu { + CommandPalette, + StatusBarWindowIndicatorMenu, +} + +function asMenuId(menu: Menu): MenuId { + switch (menu) { + case Menu.CommandPalette: return MenuId.CommandPalette; + case Menu.StatusBarWindowIndicatorMenu: return MenuId.StatusBarWindowIndicatorMenu; + } +} + interface ICommand { /** @@ -152,6 +165,13 @@ interface ICommand { */ label?: string, + /** + * The optional menus to append this command to. Only valid if `label` is + * provided as well. + * @default Menu.CommandPalette + */ + menu?: Menu | Menu[], + /** * A function that is being executed with any arguments passed over. The * return type will be send back to the caller. @@ -645,7 +665,9 @@ function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions) // Commands with labels appear in the command palette if (command.label) { - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: command.id, title: command.label } }); + for (const menu of asArray(command.menu ?? Menu.CommandPalette)) { + MenuRegistry.appendMenuItem(asMenuId(menu), { command: { id: command.id, title: command.label } }); + } } } } @@ -789,6 +811,7 @@ export { // Commands ICommand, commands, + Menu, // Branding IHomeIndicator, From 80fdee58a72707131b7573eb9cb21aefcd1efb67 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 29 Nov 2021 16:39:37 +0100 Subject: [PATCH 0131/2210] #15756 fix outdated extensions --- .../contrib/extensions/browser/extensionsWorkbenchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index ef64a93bbe698..443f77d562a79 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -201,7 +201,7 @@ class Extension implements IExtension { } get outdated(): boolean { - return !!this.gallery && this.type === ExtensionType.User && semver.gt(this.latestVersion, this.version) && this.local?.isPreReleaseVersion === this.gallery?.properties.isPreReleaseVersion; + return !!this.gallery && this.type === ExtensionType.User && semver.gt(this.latestVersion, this.version) && (this.local?.isPreReleaseVersion || !this.gallery?.properties.isPreReleaseVersion); } get telemetryData(): any { From d659e4135b68480f6025d21c6edc47a68238de95 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 29 Nov 2021 17:09:23 +0100 Subject: [PATCH 0132/2210] Fixes #137907 by disabling unicodeHighlight for the output pane. --- src/vs/workbench/contrib/output/browser/outputView.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 3ebd3590841d5..27d38915258fa 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -211,6 +211,11 @@ export class OutputEditor extends AbstractTextResourceEditor { options.padding = undefined; options.readOnly = true; options.domReadOnly = true; + options.unicodeHighlight = { + nonBasicASCII: false, + invisibleCharacters: false, + ambiguousCharacters: false, + }; const outputConfig = this.configurationService.getValue('[Log]'); if (outputConfig) { From 3604f3d8c0f6f1142633e6f44ac0e490cde15e3b Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 29 Nov 2021 17:26:57 +0100 Subject: [PATCH 0133/2210] Add Windows tests --- .../browser/controller/textAreaInput.test.ts | 338 +++++++++++++++++- 1 file changed, 329 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index 4db1fb3692087..af9317e4550b2 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -224,7 +224,7 @@ suite('TextAreaInput', () => { return outgoingEvents; } - function interpretTypeEvents(browser: IBrowser, initialState: IRecordedTextareaState, events: OutoingEvent[]): IRecordedTextareaState { + function interpretTypeEvents(OS: OperatingSystem, browser: IBrowser, initialState: IRecordedTextareaState, events: OutoingEvent[]): IRecordedTextareaState { let text = initialState.value; let selectionStart = initialState.selectionStart; let selectionEnd = initialState.selectionEnd; @@ -248,7 +248,7 @@ suite('TextAreaInput', () => { value: text, selectionStart: selectionStart, selectionEnd: selectionEnd, - selectionDirection: browser.isFirefox ? 'forward' : 'none' + selectionDirection: (browser.isFirefox || OS === OperatingSystem.Windows) ? 'forward' : 'none' }; } @@ -309,7 +309,7 @@ suite('TextAreaInput', () => { { type: 'compositionEnd' } ]); - const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); @@ -354,7 +354,7 @@ suite('TextAreaInput', () => { { type: 'compositionEnd' } ]); - const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); @@ -433,7 +433,7 @@ suite('TextAreaInput', () => { { type: 'compositionEnd' } ]); - const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); @@ -477,7 +477,7 @@ suite('TextAreaInput', () => { { type: 'compositionEnd' } ]); - const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); @@ -532,7 +532,7 @@ suite('TextAreaInput', () => { { type: 'compositionEnd' } ]); - const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); @@ -566,7 +566,7 @@ suite('TextAreaInput', () => { { type: 'type', text: 'è', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 } ]); - const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); @@ -588,7 +588,327 @@ suite('TextAreaInput', () => { { type: 'type', text: '😍', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 } ]); - const actualResultingState = interpretTypeEvents(recorded.env.browser, recorded.initial, actualOutgoingEvents); + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); assert.deepStrictEqual(actualResultingState, recorded.final); }); + + test('Windows - Chrome - Japanese using Hiragana', async () => { + // Windows, Japanese/Hiragana, type 'sennsei' and Enter + const recorded: IRecorded = { + env: { OS: OperatingSystem.Windows, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 0.80, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 0.80, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 's', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 0.90, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 's' }, + { timeStamp: 9.30, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 's', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 97.50, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 99.10, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 's', keyCode: 83, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 615.90, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 619.80, state: { value: 'aasaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 619.80, state: { value: 'aasaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せ' }, + { timeStamp: 627.70, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'せ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 719.90, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 723.60, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1816.10, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1828.30, state: { value: 'aaせaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せn', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1828.40, state: { value: 'aaせaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せn' }, + { timeStamp: 1828.70, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'せn', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1903.70, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1904.70, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 78, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2111.70, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2123.40, state: { value: 'aaせnaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せん', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2123.40, state: { value: 'aaせnaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せん' }, + { timeStamp: 2123.70, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'せん', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2215.80, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2217.10, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 78, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2968.00, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2970.00, state: { value: 'aaせんaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せんs', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2970.00, state: { value: 'aaせんaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せんs' }, + { timeStamp: 2970.20, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'input', data: 'せんs', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3079.70, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3080.70, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 's', keyCode: 83, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3295.20, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3297.10, state: { value: 'aaせんsaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せんせ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3297.20, state: { value: 'aaせんsaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せんせ' }, + { timeStamp: 3297.40, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'input', data: 'せんせ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3408.00, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3409.00, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3880.80, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3882.80, state: { value: 'aaせんせaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3882.90, state: { value: 'aaせんせaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せんせい' }, + { timeStamp: 3883.30, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'input', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3976.30, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3977.50, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'i', keyCode: 73, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 6364.90, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 6367.40, state: { value: 'aaせんせいaa', selectionStart: 2, selectionEnd: 6, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 6367.40, state: { value: 'aaせんせいaa', selectionStart: 2, selectionEnd: 6, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せんせい' }, + { timeStamp: 6367.60, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'input', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 6367.60, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'compositionend', data: 'せんせい' }, + { timeStamp: 6479.60, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 's', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 's' }, + { type: 'type', text: 'せ', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せ' }, + { type: 'type', text: 'せn', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せn' }, + { type: 'type', text: 'せん', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せん' }, + { type: 'type', text: 'せんs', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんs' }, + { type: 'type', text: 'せんせ', replacePrevCharCnt: 3, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせ' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 3, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせい' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 4, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせい' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 4, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + + test('Windows - Chrome - Korean (1)', async () => { + // Windows, Korean, type 'dkrk' and click + const recorded: IRecorded = { + env: { OS: OperatingSystem.Windows, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 23.10, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 23.10, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ㅇ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 23.20, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ㅇ' }, + { timeStamp: 23.60, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'ㅇ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 119.30, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 215.00, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 215.40, state: { value: 'aaㅇaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 215.40, state: { value: 'aaㅇaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '아' }, + { timeStamp: 215.90, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 303.20, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 511.10, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 511.70, state: { value: 'aa아aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '악', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 511.70, state: { value: 'aa아aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '악' }, + { timeStamp: 512.10, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '악', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 598.20, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 791.00, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 791.50, state: { value: 'aa악aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 791.50, state: { value: 'aa악aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '아' }, + { timeStamp: 791.80, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 791.90, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionend', data: '아' }, + { timeStamp: 792.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 792.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '가', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 792.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '가' }, + { timeStamp: 792.30, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '가', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 919.00, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2721.50, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionend', data: '가' } + ], + final: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㅇ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㅇ' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '아' }, + { type: 'type', text: '악', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '악' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '아' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: '가', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '가' }, + { type: 'type', text: '가', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + + test('Windows - Chrome - Korean (2)', async () => { + // Windows, Korean, type 'gksrmf' and Space + const recorded: IRecorded = { + env: { OS: OperatingSystem.Windows, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyG', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 23.30, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 23.50, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ㅎ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 23.50, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ㅎ' }, + { timeStamp: 27.30, state: { value: 'aaㅎaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'ㅎ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 111.80, state: { value: 'aaㅎaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyG', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 606.80, state: { value: 'aaㅎaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 607.40, state: { value: 'aaㅎaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '하', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 607.40, state: { value: 'aaㅎaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '하' }, + { timeStamp: 607.80, state: { value: 'aa하aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '하', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 705.20, state: { value: 'aa하aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1455.80, state: { value: 'aa하aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1456.40, state: { value: 'aa하aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '한', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1456.50, state: { value: 'aa하aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '한' }, + { timeStamp: 1456.90, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '한', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1567.40, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1963.10, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1963.70, state: { value: 'aa한aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '한', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1963.80, state: { value: 'aa한aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '한' }, + { timeStamp: 1963.80, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '한', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1963.90, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionend', data: '한' }, + { timeStamp: 1964.10, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 1964.10, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ㄱ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1964.10, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ㄱ' }, + { timeStamp: 1964.40, state: { value: 'aa한ㄱaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'ㄱ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2063.60, state: { value: 'aa한ㄱaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2823.60, state: { value: 'aa한ㄱaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyM', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2824.00, state: { value: 'aa한ㄱaa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: '그', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2824.10, state: { value: 'aa한ㄱaa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: '그' }, + { timeStamp: 2824.40, state: { value: 'aa한그aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '그', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2935.30, state: { value: 'aa한그aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyM', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3187.50, state: { value: 'aa한그aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyF', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3188.00, state: { value: 'aa한그aa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: '글', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3188.00, state: { value: 'aa한그aa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: '글' }, + { timeStamp: 3188.40, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '글', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3319.20, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyF', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3847.30, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3847.80, state: { value: 'aa한글aa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: '글', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3847.80, state: { value: 'aa한글aa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: '글' }, + { timeStamp: 3847.90, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '글', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3848.10, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionend', data: '글' }, + { timeStamp: 3847.70, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: ' ', keyCode: 32, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3847.80, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keypress', altKey: false, charCode: 32, code: 'Space', ctrlKey: false, isComposing: false, key: ' ', keyCode: 32, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3848.30, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: ' ', inputType: 'insertText', isComposing: false }, + { timeStamp: 3848.60, state: { value: 'aa한글 aa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'input', data: ' ', inputType: 'insertText', isComposing: false }, + { timeStamp: 3919.20, state: { value: 'aa한글 aa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3919.50, state: { value: 'aa한글 aa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: ' ', keyCode: 32, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { value: 'aa한글 aa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㅎ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㅎ' }, + { type: 'type', text: '하', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '하' }, + { type: 'type', text: '한', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '한' }, + { type: 'type', text: '한', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '한' }, + { type: 'type', text: '한', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㄱ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㄱ' }, + { type: 'type', text: '그', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '그' }, + { type: 'type', text: '글', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '글' }, + { type: 'type', text: '글', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '글' }, + { type: 'type', text: '글', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'type', text: ' ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + + test('Windows - Chrome - Chinese', async () => { + // Windows, Chinese, Type 'ni' press Space and then 'hao' and press Space. + const recorded: IRecorded = { + env: { OS: OperatingSystem.Windows, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 0.80, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 0.90, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 'n', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'n' }, + { timeStamp: 1.20, state: { value: 'aanaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'n', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 66.80, state: { value: 'aanaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 67.90, state: { value: 'aanaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 78, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 466.70, state: { value: 'aanaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 470.10, state: { value: 'aanaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ni', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 470.20, state: { value: 'aanaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ni' }, + { timeStamp: 470.50, state: { value: 'aaniaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'ni', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 563.20, state: { value: 'aaniaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 564.20, state: { value: 'aaniaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'i', keyCode: 73, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1835.00, state: { value: 'aaniaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1837.20, state: { value: 'aaniaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: '你', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1837.30, state: { value: 'aaniaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: '你' }, + { timeStamp: 1837.70, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '你', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1837.80, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionend', data: '你' }, + { timeStamp: 1914.90, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1916.10, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: ' ', keyCode: 32, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3000.40, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyH', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3000.80, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 3000.80, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'h', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3000.90, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'h' }, + { timeStamp: 3001.30, state: { value: 'aa你haa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'h', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3091.60, state: { value: 'aa你haa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyH', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3092.60, state: { value: 'aa你haa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyH', ctrlKey: false, isComposing: true, key: 'h', keyCode: 72, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3131.50, state: { value: 'aa你haa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyA', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3134.80, state: { value: 'aa你haa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ha', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3134.80, state: { value: 'aa你haa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ha' }, + { timeStamp: 3135.10, state: { value: 'aa你haaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'input', data: 'ha', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3234.90, state: { value: 'aa你haaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyA', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3236.20, state: { value: 'aa你haaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyA', ctrlKey: false, isComposing: true, key: 'a', keyCode: 65, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3491.70, state: { value: 'aa你haaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3494.80, state: { value: 'aa你haaa', selectionStart: 3, selectionEnd: 5, selectionDirection: 'forward' }, type: 'beforeinput', data: 'hao', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3495.00, state: { value: 'aa你haaa', selectionStart: 3, selectionEnd: 5, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'hao' }, + { timeStamp: 3495.40, state: { value: 'aa你haoaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'input', data: 'hao', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3570.70, state: { value: 'aa你haoaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3572.40, state: { value: 'aa你haoaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: true, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 4739.00, state: { value: 'aa你haoaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 4742.10, state: { value: 'aa你haoaa', selectionStart: 3, selectionEnd: 6, selectionDirection: 'forward' }, type: 'beforeinput', data: '好', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 4742.10, state: { value: 'aa你haoaa', selectionStart: 3, selectionEnd: 6, selectionDirection: 'forward' }, type: 'compositionupdate', data: '好' }, + { timeStamp: 4742.50, state: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '好', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 4742.60, state: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionend', data: '好' }, + { timeStamp: 4834.70, state: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 4836.00, state: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: ' ', keyCode: 32, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'n' }, + { type: 'type', text: 'ni', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ni' }, + { type: 'type', text: '你', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '你' }, + { type: 'type', text: '你', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'h', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'h' }, + { type: 'type', text: 'ha', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ha' }, + { type: 'type', text: 'hao', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'hao' }, + { type: 'type', text: '好', replacePrevCharCnt: 3, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '好' }, + { type: 'type', text: '好', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + }); From 1af6cb0edc473446aa220e9361513a26acd1b9fa Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 17:53:11 +0100 Subject: [PATCH 0134/2210] smoke test tweaks --- test/automation/src/playwrightDriver.ts | 18 ++++++++++++------ .../src/areas/workbench/data-migration.test.ts | 6 ++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index c24ef24ab48cd..eb68ebea29677 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -279,18 +279,24 @@ async function launchBrowser(options: PlaywrightOptions, endpoint: string, works } async function teardown(server: ChildProcess): Promise { + const serverPid = server.pid; + if (typeof serverPid !== 'number') { + return; + } + let retries = 0; while (retries < 3) { retries++; try { - if (typeof server.pid === 'number') { - await promisify(kill)(server.pid); - } - - return; + return await promisify(kill)(serverPid); } catch (error) { - console.warn(`Error tearing down server (pid: ${server.pid}, attempt: ${retries}): ${error}`); + try { + process.kill(serverPid, 0); // throws an exception if the process doesn't exist anymore + console.warn(`Error tearing down server (pid: ${serverPid}, attempt: ${retries}): ${error}`); + } catch (error) { + return; // Expected when process is gone + } } } diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 7c4ef52fb3a56..0c7f3c3d3ba39 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -164,6 +164,9 @@ export function setup(opts: ParsedArgs) { const backupsHome = join(userDataDir, 'Backups'); console.log('Printing backup contents (after stable app stopped):'); for await (const entry of readdirp(backupsHome)) { + if (entry.path === 'workspaces.json') { + continue; + } try { const contents = readFileSync(join(backupsHome, entry.path)).toString(); const firstLine = contents.substring(0, contents.indexOf('\n')); @@ -186,6 +189,9 @@ export function setup(opts: ParsedArgs) { console.log('Printing backup contents (after insiders app started):'); for await (const entry of readdirp(backupsHome)) { + if (entry.path === 'workspaces.json') { + continue; + } try { const contents = readFileSync(join(backupsHome, entry.path)).toString(); const firstLine = contents.substring(0, contents.indexOf('\n')); From 0d26b20c7cb8b1d4935f4e79c5e798c0c9437523 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 18:09:40 +0100 Subject: [PATCH 0135/2210] Add `CompositionContext` to track how many characters need to be replaced and have the textAreaState always reflect the state of the textarea. --- .../browser/controller/textAreaInput.ts | 127 ++++++++---------- 1 file changed, 59 insertions(+), 68 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 3b77c31aaca7a..d8134986500fc 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -127,6 +127,27 @@ export interface IBrowser { isSafari: boolean; } +class CompositionContext { + + private _lastTypeTextLength: number; + + constructor() { + this._lastTypeTextLength = 0; + } + + public handleCompositionUpdate(text: string | null | undefined): ITypeData { + text = text || ''; + const typeInput: ITypeData = { + text: text, + replacePrevCharCnt: this._lastTypeTextLength, + replaceNextCharCnt: 0, + positionDelta: 0 + }; + this._lastTypeTextLength = text.length; + return typeInput; + } +} + /** * Writes screen reader content to the textarea and is able to analyze its input events to generate: * - onCut @@ -179,7 +200,7 @@ export class TextAreaInput extends Disposable { private _selectionChangeListener: IDisposable | null; private _hasFocus: boolean; - private _isDoingComposition: boolean; + private _currentComposition: CompositionContext | null; private _nextCommand: ReadFromTextArea; constructor( @@ -197,7 +218,7 @@ export class TextAreaInput extends Disposable { this.writeScreenReaderContent('ctor'); this._hasFocus = false; - this._isDoingComposition = false; + this._currentComposition = null; this._nextCommand = ReadFromTextArea.Type; let lastKeyDown: IKeyboardEvent | null = null; @@ -205,7 +226,7 @@ export class TextAreaInput extends Disposable { this._register(this._textArea.onKeyDown((_e) => { const e = new StandardKeyboardEvent(_e); if (e.keyCode === KeyCode.KEY_IN_COMPOSITION - || (this._isDoingComposition && e.keyCode === KeyCode.Backspace)) { + || (this._currentComposition && e.keyCode === KeyCode.Backspace)) { // Stop propagation for keyDown events if the IME is processing key input e.stopPropagation(); } @@ -230,10 +251,13 @@ export class TextAreaInput extends Disposable { console.log(`[compositionstart]`, e); } - if (this._isDoingComposition) { + const currentComposition = new CompositionContext(); + if (this._currentComposition) { + // simply reset the composition context + this._currentComposition = currentComposition; return; } - this._isDoingComposition = true; + this._currentComposition = currentComposition; if ( this._OS === OperatingSystem.Macintosh @@ -251,13 +275,8 @@ export class TextAreaInput extends Disposable { if (_debugComposition) { console.log(`[compositionstart] Handling long press case on macOS + arrow key or Firefox`, e); } - this._textAreaState = new TextAreaState( - this._textAreaState.value, - this._textAreaState.selectionStart - 1, - this._textAreaState.selectionEnd, - this._textAreaState.selectionStartPosition ? new Position(this._textAreaState.selectionStartPosition.lineNumber, this._textAreaState.selectionStartPosition.column - 1) : null, - this._textAreaState.selectionEndPosition - ); + // Pretend the previous character was composed (in order to get it removed by subsequent compositionupdate events) + currentComposition.handleCompositionUpdate('x'); this._onCompositionStart.fire({ revealDeltaColumns: -1 }); return; } @@ -274,53 +293,29 @@ export class TextAreaInput extends Disposable { this._onCompositionStart.fire({ revealDeltaColumns: 0 }); })); - /** - * Deduce the typed input from a text area's value and the last observed state. - */ - const deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean): [TextAreaState, ITypeData] => { - const oldState = this._textAreaState; - const newState = TextAreaState.readFromTextArea(this._textArea); - return [newState, TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput)]; - }; - - const deduceAndroidCompositionInput = (): [TextAreaState, ITypeData] => { - const oldState = this._textAreaState; - const newState = TextAreaState.readFromTextArea(this._textArea); - return [newState, TextAreaState.deduceAndroidCompositionInput(oldState, newState)]; - }; - - /** - * Deduce the composition input from a string. - */ - const deduceComposition = (text: string): [TextAreaState, ITypeData] => { - const oldState = this._textAreaState; - const newState = TextAreaState.selectedText(text); - const typeInput: ITypeData = { - text: newState.value, - replacePrevCharCnt: oldState.selectionEnd - oldState.selectionStart, - replaceNextCharCnt: 0, - positionDelta: 0 - }; - return [newState, typeInput]; - }; - this._register(this._textArea.onCompositionUpdate((e) => { if (_debugComposition) { console.log(`[compositionupdate]`, e); } + const currentComposition = this._currentComposition; + if (!currentComposition) { + // should not be possible to receive a 'compositionupdate' without a 'compositionstart' + return; + } if (this._browser.isAndroid) { // On Android, the data sent with the composition update event is unusable. // For example, if the cursor is in the middle of a word like Mic|osoft // and Microsoft is chosen from the keyboard's suggestions, the e.data will contain "Microsoft". // This is not really usable because it doesn't tell us where the edit began and where it ended. - const [newState, typeInput] = deduceAndroidCompositionInput(); + const newState = TextAreaState.readFromTextArea(this._textArea); + const typeInput = TextAreaState.deduceAndroidCompositionInput(this._textAreaState, newState); this._textAreaState = newState; this._onType.fire(typeInput); this._onCompositionUpdate.fire(e); return; } - const [newState, typeInput] = deduceComposition(e.data || ''); - this._textAreaState = newState; + const typeInput = currentComposition.handleCompositionUpdate(e.data); + this._textAreaState = TextAreaState.readFromTextArea(this._textArea); this._onType.fire(typeInput); this._onCompositionUpdate.fire(e); })); @@ -329,36 +324,30 @@ export class TextAreaInput extends Disposable { if (_debugComposition) { console.log(`[compositionend]`, e); } - // https://github.com/microsoft/monaco-editor/issues/1663 - // On iOS 13.2, Chinese system IME randomly trigger an additional compositionend event with empty data - if (!this._isDoingComposition) { + const currentComposition = this._currentComposition; + if (!currentComposition) { + // https://github.com/microsoft/monaco-editor/issues/1663 + // On iOS 13.2, Chinese system IME randomly trigger an additional compositionend event with empty data return; } - this._isDoingComposition = false; + this._currentComposition = null; if (this._browser.isAndroid) { // On Android, the data sent with the composition update event is unusable. // For example, if the cursor is in the middle of a word like Mic|osoft // and Microsoft is chosen from the keyboard's suggestions, the e.data will contain "Microsoft". // This is not really usable because it doesn't tell us where the edit began and where it ended. - const [newState, typeInput] = deduceAndroidCompositionInput(); + const newState = TextAreaState.readFromTextArea(this._textArea); + const typeInput = TextAreaState.deduceAndroidCompositionInput(this._textAreaState, newState); this._textAreaState = newState; this._onType.fire(typeInput); this._onCompositionEnd.fire(); return; } - const [newState, typeInput] = deduceComposition(e.data || ''); - this._textAreaState = newState; + const typeInput = currentComposition.handleCompositionUpdate(e.data); + this._textAreaState = TextAreaState.readFromTextArea(this._textArea); this._onType.fire(typeInput); - - // isChrome: the textarea is not updated correctly when composition ends - // isFirefox: the textarea is not updated correctly after inserting emojis - // => we cannot assume the text at the end consists only of the composited text - if (this._browser.isChrome || this._browser.isFirefox) { - this._textAreaState = TextAreaState.readFromTextArea(this._textArea); - } - this._onCompositionEnd.fire(); })); @@ -367,11 +356,13 @@ export class TextAreaInput extends Disposable { // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received input event'); - if (this._isDoingComposition) { + if (this._currentComposition) { return; } - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/this._OS === OperatingSystem.Macintosh); + const newState = TextAreaState.readFromTextArea(this._textArea); + const typeInput = TextAreaState.deduceInput(this._textAreaState, newState, /*couldBeEmojiInput*/this._OS === OperatingSystem.Macintosh); + if (typeInput.replacePrevCharCnt === 0 && typeInput.text.length === 1 && strings.isHighSurrogate(typeInput.text.charCodeAt(0))) { // Ignore invalid input but keep it around for next time return; @@ -436,13 +427,13 @@ export class TextAreaInput extends Disposable { } })); this._register(this._textArea.onBlur(() => { - if (this._isDoingComposition) { + if (this._currentComposition) { // See https://github.com/microsoft/vscode/issues/112621 // where compositionend is not triggered when the editor // is taken off-dom during a composition // Clear the flag to be able to write to the textarea - this._isDoingComposition = false; + this._currentComposition = null; // Clear the textarea to avoid an unwanted cursor type this.writeScreenReaderContent('blurWithoutCompositionEnd'); @@ -453,12 +444,12 @@ export class TextAreaInput extends Disposable { this._setHasFocus(false); })); this._register(this._textArea.onSyntheticTap(() => { - if (this._browser.isAndroid && this._isDoingComposition) { + if (this._browser.isAndroid && this._currentComposition) { // on Android, tapping does not cancel the current composition, so the // textarea is stuck showing the old composition // Clear the flag to be able to write to the textarea - this._isDoingComposition = false; + this._currentComposition = null; // Clear the textarea to avoid an unwanted cursor type this.writeScreenReaderContent('tapWithoutCompositionEnd'); @@ -493,7 +484,7 @@ export class TextAreaInput extends Disposable { if (!this._hasFocus) { return; } - if (this._isDoingComposition) { + if (this._currentComposition) { return; } if (!this._browser.isChrome) { @@ -613,7 +604,7 @@ export class TextAreaInput extends Disposable { } public writeScreenReaderContent(reason: string): void { - if (this._isDoingComposition) { + if (this._currentComposition) { // Do not write to the text area when doing composition return; } From a16cece4e56047e4bd60628bfcd3f244a40fad82 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Nov 2021 17:58:02 +0100 Subject: [PATCH 0136/2210] fix $ref's (json pointers start with `#/`) --- .../common/tokenClassificationRegistry.ts | 2 +- .../languageConfigurationExtensionPoint.ts | 20 +++++++++---------- .../contrib/tasks/common/jsonSchema_v2.ts | 2 +- .../themes/common/colorThemeSchema.ts | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index b74f64dc33bbf..87a87855d9286 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -576,7 +576,7 @@ function getStylingSchemeEntry(description?: string, deprecationMessage?: string format: 'color-hex' }, { - $ref: '#definitions/style' + $ref: '#/definitions/style' } ] }; diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index ce14f4fb16b4a..c87e7db9858b1 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -480,9 +480,9 @@ const schema: IJSONSchema = { bracketPair: { type: 'array', items: [{ - $ref: '#definitions/openBracket' + $ref: '#/definitions/openBracket' }, { - $ref: '#definitions/closeBracket' + $ref: '#/definitions/closeBracket' }] } }, @@ -517,7 +517,7 @@ const schema: IJSONSchema = { description: nls.localize('schema.brackets', 'Defines the bracket symbols that increase or decrease the indentation.'), type: 'array', items: { - $ref: '#definitions/bracketPair' + $ref: '#/definitions/bracketPair' } }, colorizedBracketPairs: { @@ -525,7 +525,7 @@ const schema: IJSONSchema = { description: nls.localize('schema.colorizedBracketPairs', 'Defines the bracket pairs that are colorized by their nesting level if bracket pair colorization is enabled.'), type: 'array', items: { - $ref: '#definitions/bracketPair' + $ref: '#/definitions/bracketPair' } }, autoClosingPairs: { @@ -534,15 +534,15 @@ const schema: IJSONSchema = { type: 'array', items: { oneOf: [{ - $ref: '#definitions/bracketPair' + $ref: '#/definitions/bracketPair' }, { type: 'object', properties: { open: { - $ref: '#definitions/openBracket' + $ref: '#/definitions/openBracket' }, close: { - $ref: '#definitions/closeBracket' + $ref: '#/definitions/closeBracket' }, notIn: { type: 'array', @@ -566,15 +566,15 @@ const schema: IJSONSchema = { type: 'array', items: { oneOf: [{ - $ref: '#definitions/bracketPair' + $ref: '#/definitions/bracketPair' }, { type: 'object', properties: { open: { - $ref: '#definitions/openBracket' + $ref: '#/definitions/openBracket' }, close: { - $ref: '#definitions/closeBracket' + $ref: '#/definitions/closeBracket' } } }] diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 0885bf6c6dd69..71a91f23de68e 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -38,7 +38,7 @@ const shellCommand: IJSONSchema = { description: nls.localize('JsonSchema.shell', 'Specifies whether the command is a shell command or an external program. Defaults to false if omitted.') }, { - $ref: '#definitions/shellConfiguration' + $ref: '#/definitions/shellConfiguration' } ], deprecationMessage: nls.localize('JsonSchema.tasks.isShellCommand.deprecated', 'The property isShellCommand is deprecated. Use the type property of the task and the shell property in the options instead. See also the 1.14 release notes.') diff --git a/src/vs/workbench/services/themes/common/colorThemeSchema.ts b/src/vs/workbench/services/themes/common/colorThemeSchema.ts index 18b1e596074a5..0b797a38eb6bd 100644 --- a/src/vs/workbench/services/themes/common/colorThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/colorThemeSchema.ts @@ -115,8 +115,8 @@ let textMateScopes = [ ]; export const textmateColorsSchemaId = 'vscode://schemas/textmate-colors'; -export const textmateColorSettingsSchemaId = `${textmateColorsSchemaId}#definitions/settings`; -export const textmateColorGroupSchemaId = `${textmateColorsSchemaId}#definitions/colorGroup`; +export const textmateColorSettingsSchemaId = `${textmateColorsSchemaId}#/definitions/settings`; +export const textmateColorGroupSchemaId = `${textmateColorsSchemaId}#/definitions/colorGroup`; const textmateColorSchema: IJSONSchema = { type: 'array', @@ -129,7 +129,7 @@ const textmateColorSchema: IJSONSchema = { format: 'color-hex' }, { - $ref: '#definitions/settings' + $ref: '#/definitions/settings' } ] }, @@ -191,7 +191,7 @@ const textmateColorSchema: IJSONSchema = { ] }, settings: { - $ref: '#definitions/settings' + $ref: '#/definitions/settings' } }, required: [ From 8c56f0cae849839138fbd83fe4d5c7219c7ff806 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Nov 2021 18:12:51 +0100 Subject: [PATCH 0137/2210] [json/html/css] update services --- .../css-language-features/server/package.json | 2 +- .../css-language-features/server/yarn.lock | 8 ++++---- .../html-language-features/server/package.json | 4 ++-- .../html-language-features/server/yarn.lock | 16 ++++++++-------- .../json-language-features/server/package.json | 2 +- .../json-language-features/server/yarn.lock | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 0ad75a779bb2e..8d7a90e78a7fe 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -10,7 +10,7 @@ "main": "./out/node/cssServerMain", "browser": "./dist/browser/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^5.1.8", + "vscode-css-languageservice": "^5.1.9", "vscode-languageserver": "^7.0.0", "vscode-uri": "^3.0.2" }, diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 3ca64a8a544dc..9c35cda01282b 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -12,10 +12,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.43.tgz#26bcbb0595b305400e8ceaf9a127a7f905ae49c8" integrity sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ== -vscode-css-languageservice@^5.1.8: - version "5.1.8" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.8.tgz#36cb389788ffc2d5e6630ffc84e55ee38f8a2338" - integrity sha512-Si1sMykS8U/p8LYgLGPCfZD1YFT0AtvUJQp9XJGw64DZWhtwYo28G2l64USLS9ge4ZPMZpwdpOK7PfbVKfgiiA== +vscode-css-languageservice@^5.1.9: + version "5.1.9" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.9.tgz#9d473e5c61fa6d4d62719d9b8715ff6c644bf14e" + integrity sha512-/tFOWeZBL3Oc9Zc+2MAi3rEwiXJTSZsvjB+M7nSjWLbGPUIjukUA7YzLgsBoUfR35sPJYnXWUkL56PdfIYM8GA== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.16.0" diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 48f4c0f6259d9..25adfe6020a21 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,8 +9,8 @@ }, "main": "./out/node/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^5.1.8", - "vscode-html-languageservice": "^4.1.1", + "vscode-css-languageservice": "^5.1.9", + "vscode-html-languageservice": "^4.2.0", "vscode-languageserver": "^7.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-nls": "^5.0.0", diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index a9398ce6d029b..05250a637ed7d 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -12,20 +12,20 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.43.tgz#26bcbb0595b305400e8ceaf9a127a7f905ae49c8" integrity sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ== -vscode-css-languageservice@^5.1.8: - version "5.1.8" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.8.tgz#36cb389788ffc2d5e6630ffc84e55ee38f8a2338" - integrity sha512-Si1sMykS8U/p8LYgLGPCfZD1YFT0AtvUJQp9XJGw64DZWhtwYo28G2l64USLS9ge4ZPMZpwdpOK7PfbVKfgiiA== +vscode-css-languageservice@^5.1.9: + version "5.1.9" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.9.tgz#9d473e5c61fa6d4d62719d9b8715ff6c644bf14e" + integrity sha512-/tFOWeZBL3Oc9Zc+2MAi3rEwiXJTSZsvjB+M7nSjWLbGPUIjukUA7YzLgsBoUfR35sPJYnXWUkL56PdfIYM8GA== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.16.0" vscode-nls "^5.0.0" vscode-uri "^3.0.2" -vscode-html-languageservice@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-4.1.1.tgz#93739c9f3d0c12c8249bad23f5005850c289ec38" - integrity sha512-rrDyCiOgMwOPgchpPGAeLzjYVVEW/Ror2/a1BWUEI3S9+NQhA9vj4SQkzmH6g2Bq9S9SV0OQeadD+xphOf1N3w== +vscode-html-languageservice@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-4.2.0.tgz#87ea1c659b40ae0b6f6627df75e5d25650977d30" + integrity sha512-5ebk/5kMa7PrCPL3JuP27vo8h+coDgSkMP14pSlKz3ISXZxHm+nnCenhVrpy9Ayamtwb28YXeQuN8AqNQH8kVQ== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.16.0" diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 90ec7eb10d330..988c2963e6b6c 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,7 +14,7 @@ "dependencies": { "jsonc-parser": "^3.0.0", "request-light": "^0.5.4", - "vscode-json-languageservice": "^4.2.0-next.1", + "vscode-json-languageservice": "^4.2.0-next.2", "vscode-languageserver": "^7.0.0", "vscode-uri": "^3.0.2" }, diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 9b542a82f9c5e..68bffbb9371c8 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -22,10 +22,10 @@ request-light@^0.5.4: resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.4.tgz#497a98c6d8ae49536417a5e2d7f383b934f3e38c" integrity sha512-t3566CMweOFlUk7Y1DJMu5OrtpoZEb6aSTsLQVT3wtrIEJ5NhcY9G/Oqxvjllzl4a15zXfFlcr9q40LbLVQJqw== -vscode-json-languageservice@^4.2.0-next.1: - version "4.2.0-next.1" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.0-next.1.tgz#31a8c3be04c87d5aa593c11b98d84258b173a22f" - integrity sha512-aQvkkuZpeSPv86QLzyMdKTCgvXR+qSO39nSgj/XGaOcuHmTt7vMZB7ymYGGkQ4cAaQdHs/2G6a479LQybIGSbg== +vscode-json-languageservice@^4.2.0-next.2: + version "4.2.0-next.2" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.0-next.2.tgz#9e47e06e8228948ddbc84ecd29d3fa724f3a2ea0" + integrity sha512-P0sdiZS7bM8+bxrkpL7XPwwhmZj94pcJIAZUh/QeessvYtXFnRmOEybe20rC+CS7b7DfwFcVt0p4p93hGZQ5gg== dependencies: jsonc-parser "^3.0.0" vscode-languageserver-textdocument "^1.0.1" From 1d0a7f2804bd9ad47b03d6e2889688d8d8dc52e7 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 29 Nov 2021 09:54:40 -0800 Subject: [PATCH 0138/2210] fixes #136802 --- src/vs/workbench/browser/parts/views/viewsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts index 4ecd0454eb058..6820de6a049b7 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -124,7 +124,7 @@ export class ViewsService extends Disposable implements IViewsService { private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void { this.deregisterPaneComposite(viewContainer, from); - this.registerPaneComposite(viewContainer, to); + setTimeout(() => this.registerPaneComposite(viewContainer, to), 0); } private onViewDescriptorsAdded(views: ReadonlyArray, container: ViewContainer): void { From f32a371f70a7784f953fd69470a3c0486028e23d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 29 Nov 2021 19:22:09 +0100 Subject: [PATCH 0139/2210] :up: @parcel/watcher (fix #136460) --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index bc145ad8c0b15..29e6125949068 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ }, "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", - "@parcel/watcher": "2.0.3", + "@parcel/watcher": "2.0.4", "@vscode/sqlite3": "4.0.12", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/remote/package.json b/remote/package.json index cdab215f1c614..662ce3568ab72 100644 --- a/remote/package.json +++ b/remote/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", - "@parcel/watcher": "2.0.3", + "@parcel/watcher": "2.0.4", "@vscode/vscode-languagedetection": "1.0.21", "applicationinsights": "1.0.8", "cookie": "^0.4.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index d59133c86f410..959b0f97cd592 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -83,10 +83,10 @@ resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.4.tgz#40e1c0ad20743fcee1604a7df2c57faf0aa1af87" integrity sha512-Ot53G927ykMF8cQ3/zq4foZtdk+Tt1YpX7aUTHxBU7UHNdkEiBvBfZSq+rnlUmKCJ19VatwPG4mNzvcGpBj4og== -"@parcel/watcher@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.3.tgz#2bae7720f2b9c21ea0b89bab55479c7e8937231e" - integrity sha512-PHh5PArr3nYGYVj9z/NSfDmmKEBNrg2bzoFgxzjTRBBxPUKx039x3HF6VGLFIfrghjJxcYn/IeSpdVwfob7KFA== +"@parcel/watcher@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" + integrity sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg== dependencies: node-addon-api "^3.2.1" node-gyp-build "^4.3.0" diff --git a/yarn.lock b/yarn.lock index ecfdc31534d93..83066307eed1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -510,10 +510,10 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.0.3.tgz#13a12ae9e05c2a782f7b5e84c3cbfda4225eaf80" integrity sha512-puWxACExDe9nxbBB3lOymQFrLYml2dVOrd7USiVRnSbgXE+KwBu+HxFvxrzfqsiSda9IWsXJG1ef7C1O2/GmKQ== -"@parcel/watcher@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.3.tgz#2bae7720f2b9c21ea0b89bab55479c7e8937231e" - integrity sha512-PHh5PArr3nYGYVj9z/NSfDmmKEBNrg2bzoFgxzjTRBBxPUKx039x3HF6VGLFIfrghjJxcYn/IeSpdVwfob7KFA== +"@parcel/watcher@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" + integrity sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg== dependencies: node-addon-api "^3.2.1" node-gyp-build "^4.3.0" From c75a35cfab29b1fbbf75efcc6d439ac10eb6ac74 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 29 Nov 2021 10:26:42 -0800 Subject: [PATCH 0140/2210] fix padding for web layout control --- .../parts/titlebar/media/titlebarpart.css | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 042cd52fe802a..3a31b368db3b2 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -132,14 +132,23 @@ margin-left: auto; } -.monaco-workbench.mac:not(web) .part.titlebar > .window-controls-container { +.monaco-workbench .part.titlebar > .window-controls-container.show-layout-control { + width: 160px; +} + +.monaco-workbench.web .part.titlebar > .window-controls-container.show-layout-control { + width: 28px; + padding-right: 8px; +} + +.monaco-workbench.mac:not(.web) .part.titlebar > .window-controls-container { position: absolute; right: 8px; width: 28px; display: none; } -.monaco-workbench.mac:not(web) .part.titlebar > .window-controls-container.show-layout-control { +.monaco-workbench.mac:not(.web) .part.titlebar > .window-controls-container.show-layout-control { display: flex; } @@ -148,10 +157,6 @@ background-color: transparent; } -.monaco-workbench .part.titlebar > .window-controls-container.show-layout-control { - width: 160px; -} - .monaco-workbench .part.titlebar > .window-controls-container > .layout-dropdown-container { display: none; } From 10e7fa19754b2de40b49e888a368e8b363f17bb4 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 29 Nov 2021 10:30:35 -0800 Subject: [PATCH 0141/2210] make show more italic. --- src/vs/workbench/contrib/notebook/browser/media/notebook.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 9bbb57d3722aa..0cca53087ecc5 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -871,6 +871,7 @@ .output-show-more { padding: 8px 0 0 0; + font-style: italic; } .cell-contributed-items.cell-contributed-items-left { From 8fc6c5205933aac15e13dd26f62875baa7da9398 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 29 Nov 2021 10:40:39 -0800 Subject: [PATCH 0142/2210] Update model resolution. --- .../contrib/notebook/browser/notebook.layout.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.layout.md b/src/vs/workbench/contrib/notebook/browser/notebook.layout.md index 012846156a171..83601355275c0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.layout.md +++ b/src/vs/workbench/contrib/notebook/browser/notebook.layout.md @@ -4,6 +4,15 @@ The notebook editor is a virtualized list view rendered in two contexts (mainfra ## Notebook model resolution +The notebook model resolution consists of two main parts + +* Resolving the raw data (bytes) of the resource from file service. This part is backed by the `WorkingCopyService` and it will resolve the data and broadcast updates when the resource is updated on file system. +* Requesting the contributed notebook serializer to serialize/deserize the raw bytes for the resource. We will find the best matched notebook serializer (by user's editor type configuration and serializer's selector defintion) and convert the raw bytes from/to `NotebookTextModel`. + +`NotebookTextModel` is the only source of truth for the notebook document once the resource is opened in the workspace. The source text of each individual cell in the notebook is backed by a piece tree text buffer. When the notebook is opened in the editor group, we will request the `TextModelResolverModelService` for the monaco `TextModel` reference for each cell. The `TextModel` will use the piece tree text buffer from the cell as the backing store so whenver the `TextModel` gets udpated, the cells in `NotebookTextModel` are always up to date. + +Since we are using the `TextModelResolverModelService` for cell's text model resolution, the `TextModel`s will have a mirror in the extension host, just like a normal resource opened in a text editor. Extensions can treat them as normal text documents. + ![arch](https://user-images.githubusercontent.com/876920/141845889-abe0384e-0093-4b08-831a-04424a4b8101.png) ## Viewport rendering From 2f066a367501b3a77a3c9714aeaddb1fba034ed9 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 19:49:59 +0100 Subject: [PATCH 0143/2210] Initialize state explicitly --- .../browser/controller/textAreaInput.ts | 5 ++++ .../browser/controller/textAreaInput.test.ts | 27 +++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index d8134986500fc..a537b826df4a5 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -460,6 +460,11 @@ export class TextAreaInput extends Disposable { })); } + _initializeFromTest(): void { + this._hasFocus = true; + this._textAreaState = TextAreaState.readFromTextArea(this._textArea); + } + private _installSelectionChangeListener(): IDisposable { // See https://github.com/microsoft/vscode/issues/27216 and https://github.com/microsoft/vscode/issues/98256 // When using a Braille display, it is possible for users to reposition the diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index af9317e4550b2..dd8e3f8e08a88 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -47,14 +47,7 @@ suite('TextAreaInput', () => { throw new Error('Function not implemented.'); }, getScreenReaderContent: function (currentState: TextAreaState): TextAreaState { - return new TextAreaState( - recorded.initial.value, - recorded.initial.selectionStart, - recorded.initial.selectionEnd, - null, - null - ); - // return TextAreaState.selectedText(''); + return new TextAreaState('', 0, 0, null, null); }, deduceModelPosition: function (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position { throw new Error('Function not implemented.'); @@ -88,10 +81,7 @@ suite('TextAreaInput', () => { readonly onCut = Event.None; readonly onCopy = Event.None; readonly onPaste = Event.None; - - readonly _onFocus = this._register(new Emitter()); - readonly onFocus = this._onFocus.event; - + readonly onFocus = Event.None; readonly onBlur = Event.None; readonly onSyntheticTap = Event.None; @@ -107,7 +97,13 @@ suite('TextAreaInput', () => { }; } - public dispatchRecordedEvent(event: IRecordedEvent): void { + public _initialize(state: IRecordedTextareaState): void { + this._state.value = state.value; + this._state.selectionStart = state.selectionStart; + this._state.selectionEnd = state.selectionEnd; + } + + public _dispatchRecordedEvent(event: IRecordedEvent): void { this._state.value = event.state.value; this._state.selectionStart = event.state.selectionStart; this._state.selectionEnd = event.state.selectionEnd; @@ -193,7 +189,8 @@ suite('TextAreaInput', () => { }); const input = disposables.add(new TextAreaInput(host, wrapper, recorded.env.OS, recorded.env.browser)); - wrapper._onFocus.fire(null as any); + wrapper._initialize(recorded.initial); + input._initializeFromTest(); let outgoingEvents: OutoingEvent[] = []; @@ -217,7 +214,7 @@ suite('TextAreaInput', () => { }))); for (const event of recorded.events) { - wrapper.dispatchRecordedEvent(event); + wrapper._dispatchRecordedEvent(event); await yieldNow(); } From 45b3e8ad6cb8f3c46b77731518f9e92cce5bb611 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 29 Nov 2021 11:15:13 -0800 Subject: [PATCH 0144/2210] fixes #136805 --- src/vs/workbench/browser/parts/compositeBar.ts | 2 +- src/vs/workbench/browser/parts/panel/panelPart.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 5d1157b89357e..4cd79b5b3e0fd 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -428,7 +428,7 @@ export class CompositeBar extends Widget implements ICompositeBar { // Case: we closed the last visible composite // Solv: we hide the part - else if (this.visibleComposites.length === 0) { + else if (this.visibleComposites.length <= 1) { this.options.hidePart(); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index d9c9b83f49d65..7136f77f87840 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -212,7 +212,6 @@ export abstract class BasePanelPart extends CompositePart impleme const activePanel = this.getActivePaneComposite(); const isActive = activePanel?.getId() === panel.id || - (!activePanel && this.getLastActivePaneCompositeId() === panel.id) || (this.extensionsRegistered && this.compositeBar.getVisibleComposites().length === 0); if (isActive || !this.shouldBeHidden(panel.id, cachedPanel)) { From db1f865720fe239f2503dc18b6c6eeda177c42fe Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 29 Nov 2021 10:29:48 -0800 Subject: [PATCH 0145/2210] Don't use empty lines when counting new indent Polish for https://github.com/microsoft/vscode/issues/138063 --- extensions/emmet/src/removeTag.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/emmet/src/removeTag.ts b/extensions/emmet/src/removeTag.ts index 5e46eff08e6a3..c46ac5db52790 100644 --- a/extensions/emmet/src/removeTag.ts +++ b/extensions/emmet/src/removeTag.ts @@ -25,7 +25,7 @@ export function removeTag() { return editor.edit(editBuilder => { finalRangesToRemove.forEach(range => { - editBuilder.replace(range, ''); + editBuilder.delete(range); }); }); } @@ -77,8 +77,11 @@ function calculateIndentAmountToRemove(document: vscode.TextDocument, openRange: let contentIndent: number | undefined; for (let i = startLine + 1; i < endLine; i++) { - const lineIndent = document.lineAt(i).firstNonWhitespaceCharacterIndex; - contentIndent = !contentIndent ? lineIndent : Math.min(contentIndent, lineIndent); + const line = document.lineAt(i); + if (!line.isEmptyOrWhitespace) { + const lineIndent = line.firstNonWhitespaceCharacterIndex; + contentIndent = !contentIndent ? lineIndent : Math.min(contentIndent, lineIndent); + } } let indentAmount = 0; From bdc04a9b224f3ccd43f8797fb1052972b3bb23f8 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 29 Nov 2021 11:22:40 -0800 Subject: [PATCH 0146/2210] Trim inner tag leading and trailing whitespace Fixes https://github.com/microsoft/vscode/issues/138063 --- extensions/emmet/src/removeTag.ts | 63 ++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/extensions/emmet/src/removeTag.ts b/extensions/emmet/src/removeTag.ts index c46ac5db52790..c43a9a03e7239 100644 --- a/extensions/emmet/src/removeTag.ts +++ b/extensions/emmet/src/removeTag.ts @@ -51,20 +51,79 @@ function getRangesToRemove(document: vscode.TextDocument, rootNode: HtmlFlatNode closeTagRange = offsetRangeToVsRange(document, nodeToUpdate.close.start, nodeToUpdate.close.end); } + if (openTagRange && closeTagRange) { + const innerCombinedRange = new vscode.Range( + openTagRange.end.line, + openTagRange.end.character, + closeTagRange.start.line, + closeTagRange.start.character); + const outerCombinedRange = new vscode.Range( + openTagRange.start.line, + openTagRange.start.character, + closeTagRange.end.line, + closeTagRange.end.character); + // Special case: there is only whitespace in between. + if (document.getText(innerCombinedRange).trim() === '' && nodeToUpdate.name !== 'pre') { + return [outerCombinedRange]; + } + } + let rangesToRemove = []; if (openTagRange) { rangesToRemove.push(openTagRange); if (closeTagRange) { const indentAmountToRemove = calculateIndentAmountToRemove(document, openTagRange, closeTagRange); + let firstInnerNonEmptyLine: number | undefined; + let lastInnerNonEmptyLine: number | undefined; for (let i = openTagRange.start.line + 1; i < closeTagRange.start.line; i++) { - rangesToRemove.push(new vscode.Range(i, 0, i, indentAmountToRemove)); + if (!document.lineAt(i).isEmptyOrWhitespace) { + rangesToRemove.push(new vscode.Range(i, 0, i, indentAmountToRemove)); + if (firstInnerNonEmptyLine === undefined) { + // We found the first non-empty inner line. + firstInnerNonEmptyLine = i; + } + lastInnerNonEmptyLine = i; + } + } + + // Remove the entire last line + empty lines preceding it + // if it is just the tag, otherwise remove just the tag. + if (entireLineIsTag(document, closeTagRange) && lastInnerNonEmptyLine) { + rangesToRemove.push(new vscode.Range( + lastInnerNonEmptyLine, + document.lineAt(lastInnerNonEmptyLine).range.end.character, + closeTagRange.end.line, + closeTagRange.end.character)); + } else { + rangesToRemove.push(closeTagRange); + } + + // Remove the entire first line + empty lines proceding it + // if it is just the tag, otherwise keep on removing just the tag. + if (entireLineIsTag(document, openTagRange) && firstInnerNonEmptyLine) { + rangesToRemove[1] = new vscode.Range( + openTagRange.start.line, + openTagRange.start.character, + firstInnerNonEmptyLine, + document.lineAt(firstInnerNonEmptyLine).firstNonWhitespaceCharacterIndex); + rangesToRemove.shift(); } - rangesToRemove.push(closeTagRange); } } return rangesToRemove; } +function entireLineIsTag(document: vscode.TextDocument, range: vscode.Range): boolean { + if (range.start.line === range.end.line) { + const lineText = document.lineAt(range.start).text; + const tagText = document.getText(range); + if (lineText.trim() === tagText) { + return true; + } + } + return false; +} + /** * Calculates the amount of indent to remove for getRangesToRemove. */ From d838a004ff3c7ae765f84e5f820a1b4655a411ff Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 29 Nov 2021 11:29:31 -0800 Subject: [PATCH 0147/2210] add experimental tag to layout control setting --- src/vs/workbench/browser/workbench.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index cfc143254796a..dcbb417cc47bd 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -363,7 +363,8 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.experimental.layoutControl.enabled': { 'type': 'boolean', - 'default': product.quality !== 'stable', + 'tags': ['experimental'], + 'default': product.quality !== 'stable ', 'description': localize('layoutControlEnabled', "Controls whether the layout control button in the custom title bar is enabled."), }, 'workbench.experimental.sidePanel.enabled': { From e208c8604ea532b3c031a1dbbff4e198f3cc2f70 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:46:57 -0800 Subject: [PATCH 0148/2210] Remove timeout from automation terminal util I don't believe this was ever required, awaiting writeInTerminal should make sure the data event is triggered in xterm, the following enter should just trigger another data event. This doesn't guarantee it was accepted by the process yet, just that xterm.js emitted the event. Part of #137847 --- test/automation/src/terminal.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index b87457f4ea187..947ff2fe924ba 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -80,8 +80,6 @@ export class Terminal { async runCommandInTerminal(commandText: string): Promise { await this.code.writeInTerminal(Selector.Xterm, commandText); - // hold your horses - await new Promise(c => setTimeout(c, 500)); await this.code.dispatchKeybinding('enter'); } From d5bfd6d31ce2eab67cd4d2de880e7913a577904a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:48:14 -0800 Subject: [PATCH 0149/2210] Remove unneeded logs in test --- test/automation/src/terminal.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 947ff2fe924ba..5b9ff1c101258 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -113,11 +113,9 @@ export class Terminal { async getTerminalGroups(): Promise { const tabCount = (await this.code.waitForElements(Selector.Tabs, true)).length; - console.log('tabCount', tabCount); const groups: TerminalGroup[] = []; for (let i = 0; i < tabCount; i++) { const instance = await this.code.waitForElement(`${Selector.Tabs}[data-index="${i}"] ${Selector.TabsEntry}`); - console.log('instance', instance); const label: TerminalLabel = { name: instance.textContent.replace(/^[├┌└]\s*/, '') }; @@ -128,7 +126,6 @@ export class Terminal { groups.push([label]); } } - console.log('groups', groups); return groups; } From dd56064202fbdc6cf530d007b236b6e9e3c63237 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 29 Nov 2021 12:09:57 -0800 Subject: [PATCH 0150/2210] Fix Emmet removeTag tests Ref https://github.com/microsoft/vscode/issues/138063 --- extensions/emmet/src/test/tagActions.test.ts | 41 +++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/extensions/emmet/src/test/tagActions.test.ts b/extensions/emmet/src/test/tagActions.test.ts index 9406929905a9a..c9f8a7fe4fea2 100644 --- a/extensions/emmet/src/test/tagActions.test.ts +++ b/extensions/emmet/src/test/tagActions.test.ts @@ -27,6 +27,22 @@ suite('Tests for Emmet actions on html tags', () => { `; + const spacedContents = ` +
+
    + +
  • Hello
  • + +
  • There
  • + +
  • Bye
  • + + +
+ +
+ `; + let contentsWithTemplate = ` `; @@ -187,6 +201,29 @@ suite('Tests for Emmet actions on html tags', () => { }); }); }); + + test('remove tag with extra trim', () => { + const expectedContents = ` +
+
  • Hello
  • + +
  • There
  • + +
  • Bye
  • + +
    + `; + return withRandomFileEditor(spacedContents, 'html', (editor, doc) => { + editor.selections = [ + new Selection(2, 4, 2, 4), // cursor inside ul tag + ]; + + return removeTag()!.then(() => { + assert.strictEqual(doc.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); // #endregion // #region split/join tag From 0685366bdfee8b0037d8c7c6b4823d616f8aecb7 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 21:37:37 +0100 Subject: [PATCH 0151/2210] Small tweaks to input handler --- src/vs/editor/browser/controller/textAreaInput.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index a537b826df4a5..262ac40cf44f2 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -352,6 +352,10 @@ export class TextAreaInput extends Disposable { })); this._register(this._textArea.onInput((e) => { + if (_debugComposition) { + console.log(`[input]`, e); + } + // Pretend here we touched the text area, as the `input` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received input event'); @@ -369,12 +373,18 @@ export class TextAreaInput extends Disposable { } this._textAreaState = newState; + const typeInputIsNoOp = ( + typeInput.text === '' + && typeInput.replacePrevCharCnt === 0 + && typeInput.replaceNextCharCnt === 0 + && typeInput.positionDelta === 0 + ); if (this._nextCommand === ReadFromTextArea.Type) { - if (typeInput.text !== '' || typeInput.replacePrevCharCnt !== 0) { + if (!typeInputIsNoOp) { this._onType.fire(typeInput); } } else { - if (typeInput.text !== '' || typeInput.replacePrevCharCnt !== 0) { + if (!typeInputIsNoOp) { this._firePaste(typeInput.text, null); } this._nextCommand = ReadFromTextArea.Type; From 6191026e27dc321fc4d78e123a297432baaadb3c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Nov 2021 21:38:40 +0100 Subject: [PATCH 0152/2210] server cli: use deprecated option --- src/vs/platform/environment/node/argv.ts | 5 ++++- src/vs/platform/environment/node/argvHelper.ts | 3 +++ src/vs/server/remoteExtensionHostAgent.ts | 4 ++++ src/vs/server/remoteExtensionHostAgentServer.ts | 6 +----- src/vs/server/serverEnvironmentService.ts | 9 ++------- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 75d91f3eebf1e..135c53c78fd98 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -155,11 +155,13 @@ export const OPTIONS: OptionDescriptions> = { export interface ErrorReporter { onUnknownOption(id: string): void; onMultipleValues(id: string, usedValue: string): void; + onDeprecatedOption(deprecatedId: string, currentId: string): void; } const ignoringReporter: ErrorReporter = { onUnknownOption: () => { }, - onMultipleValues: () => { } + onMultipleValues: () => { }, + onDeprecatedOption: () => { } }; export function parseArgs(args: string[], options: OptionDescriptions, errorReporter: ErrorReporter = ignoringReporter): T { @@ -205,6 +207,7 @@ export function parseArgs(args: string[], options: OptionDescriptions, err if (o.deprecates && remainingArgs.hasOwnProperty(o.deprecates)) { if (!val) { val = remainingArgs[o.deprecates]; + errorReporter.onDeprecatedOption(o.deprecates, optionId); } delete remainingArgs[o.deprecates]; } diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index e47424d2a749b..4dfdb5144b495 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -17,6 +17,9 @@ function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): Nativ }, onMultipleValues: (id, val) => { console.warn(localize('multipleValues', "Option '{0}' is defined more than once. Using value '{1}.'", id, val)); + }, + onDeprecatedOption: (deprecatedOption: string, actualOption: string) => { + console.warn(localize('deprecatedArgument', "Option '{0}' is deprecated, please use '{1}' instead", deprecatedOption, actualOption)); } }; diff --git a/src/vs/server/remoteExtensionHostAgent.ts b/src/vs/server/remoteExtensionHostAgent.ts index 1c45e1e21ed42..0180f67d5a404 100644 --- a/src/vs/server/remoteExtensionHostAgent.ts +++ b/src/vs/server/remoteExtensionHostAgent.ts @@ -25,6 +25,10 @@ const errorReporter: ErrorReporter = { onUnknownOption: (id: string) => { console.error(`Ignoring option ${id}: not supported for server.`); + }, + + onDeprecatedOption: (deprecatedOption: string, actualOption: string) => { + console.warn(`Option '${deprecatedOption}' is deprecated, please use '${actualOption}' instead`); } }; diff --git a/src/vs/server/remoteExtensionHostAgentServer.ts b/src/vs/server/remoteExtensionHostAgentServer.ts index 360e4625d94b6..7180765ad62d9 100644 --- a/src/vs/server/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/remoteExtensionHostAgentServer.ts @@ -918,10 +918,6 @@ export class RemoteExtensionHostAgentServer extends Disposable { } function parseConnectionToken(args: ServerParsedArgs): { connectionToken: string; connectionTokenIsMandatory: boolean; } { - if (args['connectionToken']) { - console.warn(`The argument '--connectionToken' is deprecated, please use '--connection-token' instead`); - } - if (args['connection-secret']) { if (args['connection-token']) { console.warn(`Please do not use the argument '--connection-token' at the same time as '--connection-secret'.`); @@ -935,7 +931,7 @@ function parseConnectionToken(args: ServerParsedArgs): { connectionToken: string } return { connectionToken: rawConnectionToken, connectionTokenIsMandatory: true }; } else { - return { connectionToken: args['connection-token'] || args['connectionToken'] || generateUuid(), connectionTokenIsMandatory: false }; + return { connectionToken: args['connection-token'] || generateUuid(), connectionTokenIsMandatory: false }; } } diff --git a/src/vs/server/serverEnvironmentService.ts b/src/vs/server/serverEnvironmentService.ts index 86aa2f00eef29..32cff4f20faae 100644 --- a/src/vs/server/serverEnvironmentService.ts +++ b/src/vs/server/serverEnvironmentService.ts @@ -12,9 +12,8 @@ import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/envi export const serverOptions: OptionDescriptions = { 'port': { type: 'string' }, 'pick-port': { type: 'string' }, - 'connectionToken': { type: 'string' }, // deprecated in favor of `--connection-token` - 'connection-token': { type: 'string', description: nls.localize('connection-token', "A secret that must be included by the web client with all requests.") }, - 'connection-secret': { type: 'string', description: nls.localize('connection-secret', "Path to file that contains the connection token. This will require that all incoming connections know the secret.") }, + 'connection-token': { type: 'string', cat: 'o', deprecates: 'connectionToken', description: nls.localize('connection-token', "A secret that must be included by the web client with all requests.") }, + 'connection-secret': { type: 'string', cat: 'o', description: nls.localize('connection-secret', "Path to file that contains the connection token. This will require that all incoming connections know the secret.") }, 'host': { type: 'string' }, 'socket-path': { type: 'string' }, 'driver': { type: 'string' }, @@ -65,10 +64,6 @@ export const serverOptions: OptionDescriptions = { export interface ServerParsedArgs { port?: string; 'pick-port'?: string; - /** - * @deprecated use `connection-token` instead - */ - connectionToken?: string; /** * A secret token that must be provided by the web client with all requests. * Use only `[0-9A-Za-z\-]`. From bc3f2d9dd7507e36e28b3bfca93b67090ccd47dc Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 29 Nov 2021 12:43:41 -0800 Subject: [PATCH 0153/2210] set default to false for layout control with new setting --- src/vs/workbench/browser/workbench.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index dcbb417cc47bd..f491019dfb7be 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -364,7 +364,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'workbench.experimental.layoutControl.enabled': { 'type': 'boolean', 'tags': ['experimental'], - 'default': product.quality !== 'stable ', + 'default': false, 'description': localize('layoutControlEnabled', "Controls whether the layout control button in the custom title bar is enabled."), }, 'workbench.experimental.sidePanel.enabled': { From e87368e5cff61bf3dcd193b2c2198fd34daef2ff Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 22:08:57 +0100 Subject: [PATCH 0154/2210] Add latest Windows 11 recordings --- .../browser/controller/textAreaInput.test.ts | 331 ++++++++++++++++++ 1 file changed, 331 insertions(+) diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index dd8e3f8e08a88..144975d784686 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -675,6 +675,92 @@ suite('TextAreaInput', () => { assert.deepStrictEqual(actualResultingState, recorded.final); }); + test('Windows 11 - Chrome - Japanese using Hiragana', async () => { + // Windows, Japanese/Hiragana, type 'sennsei' and Enter + const recorded: IRecorded = { + env: { OS: OperatingSystem.Windows, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 15.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 15.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 's', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 15.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 's' }, + { timeStamp: 20.00, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 's', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 111.00, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 111.00, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 's', keyCode: 83, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 832.00, state: { value: 'aasaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 839.00, state: { value: 'aasaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 839.00, state: { value: 'aasaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せ' }, + { timeStamp: 890.00, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'せ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 936.00, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 937.00, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1456.00, state: { value: 'aaせaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1460.00, state: { value: 'aaせaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せn', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1460.00, state: { value: 'aaせaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せn' }, + { timeStamp: 1461.00, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'せn', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1522.00, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1522.00, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 78, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1684.00, state: { value: 'aaせnaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1694.00, state: { value: 'aaせnaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せん', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1694.00, state: { value: 'aaせnaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せん' }, + { timeStamp: 1694.00, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'せん', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1763.00, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1763.00, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 78, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1873.00, state: { value: 'aaせんaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1878.00, state: { value: 'aaせんaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せんs', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1878.00, state: { value: 'aaせんaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せんs' }, + { timeStamp: 1878.00, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'input', data: 'せんs', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1969.00, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1969.00, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 's', keyCode: 83, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2094.00, state: { value: 'aaせんsaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2111.00, state: { value: 'aaせんsaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せんせ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2111.00, state: { value: 'aaせんsaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せんせ' }, + { timeStamp: 2111.00, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'input', data: 'せんせ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2222.00, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2222.00, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyE', ctrlKey: false, isComposing: true, key: 'e', keyCode: 69, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2356.00, state: { value: 'aaせんせaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2367.00, state: { value: 'aaせんせaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2367.00, state: { value: 'aaせんせaa', selectionStart: 2, selectionEnd: 5, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せんせい' }, + { timeStamp: 2367.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'input', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2456.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2456.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'i', keyCode: 73, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3776.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3776.00, state: { value: 'aaせんせいaa', selectionStart: 2, selectionEnd: 6, selectionDirection: 'forward' }, type: 'beforeinput', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3776.00, state: { value: 'aaせんせいaa', selectionStart: 2, selectionEnd: 6, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'せんせい' }, + { timeStamp: 3785.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'input', data: 'せんせい', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3785.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'compositionend', data: 'せんせい' }, + { timeStamp: 3886.00, state: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { value: 'aaせんせいaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, ([ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 's', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 's' }, + { type: 'type', text: 'せ', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せ' }, + { type: 'type', text: 'せn', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せn' }, + { type: 'type', text: 'せん', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せん' }, + { type: 'type', text: 'せんs', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんs' }, + { type: 'type', text: 'せんせ', replacePrevCharCnt: 3, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせ' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 3, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせい' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 4, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'せんせい' }, + { type: 'type', text: 'せんせい', replacePrevCharCnt: 4, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ])); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + test('Windows - Chrome - Korean (1)', async () => { // Windows, Korean, type 'dkrk' and click const recorded: IRecorded = { @@ -736,6 +822,78 @@ suite('TextAreaInput', () => { assert.deepStrictEqual(actualResultingState, recorded.final); }); + test('Windows 11 - Chrome - Korean (1)', async () => { + // Windows, Korean, type 'dkrk' and Space + const recorded: IRecorded = { + env: { OS: OperatingSystem.Windows, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 9.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 10.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ㅇ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 10.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ㅇ' }, + { timeStamp: 26.00, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'ㅇ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 119.00, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 134.00, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyD', ctrlKey: false, isComposing: true, key: 'd', keyCode: 68, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 442.00, state: { value: 'aaㅇaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 442.00, state: { value: 'aaㅇaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 442.00, state: { value: 'aaㅇaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '아' }, + { timeStamp: 451.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 535.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 535.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'k', keyCode: 75, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 879.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 879.00, state: { value: 'aa아aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '악', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 879.00, state: { value: 'aa아aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '악' }, + { timeStamp: 881.00, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '악', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 980.00, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 992.00, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'r', keyCode: 82, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1230.00, state: { value: 'aa악aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1230.00, state: { value: 'aa악aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1230.00, state: { value: 'aa악aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '아' }, + { timeStamp: 1242.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '아', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1242.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionend', data: '아' }, + { timeStamp: 1242.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 1242.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '가', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1242.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '가' }, + { timeStamp: 1243.00, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '가', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1375.00, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1375.00, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'k', keyCode: 75, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3412.00, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionend', data: '가' }, + { timeStamp: 3412.00, state: { value: 'aa아가aa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: null, inputType: 'deleteContentBackward', isComposing: false }, + { timeStamp: 3413.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: null, inputType: 'deleteContentBackward', isComposing: false }, + { timeStamp: 3413.00, state: { value: 'aa아aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '가', inputType: 'insertText', isComposing: false }, + { timeStamp: 3414.00, state: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '가', inputType: 'insertText', isComposing: false } + ], + final: { value: 'aa아가aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㅇ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㅇ' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '아' }, + { type: 'type', text: '악', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '악' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '아' }, + { type: 'type', text: '아', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: '가', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '가' }, + { type: 'type', text: '가', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'type', text: '', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'type', text: '가', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + test('Windows - Chrome - Korean (2)', async () => { // Windows, Korean, type 'gksrmf' and Space const recorded: IRecorded = { @@ -824,6 +982,95 @@ suite('TextAreaInput', () => { assert.deepStrictEqual(actualResultingState, recorded.final); }); + test('Windows 11 - Chrome - Korean (2)', async () => { + // Windows, Korean, type 'gksrmf' and Space + const recorded: IRecorded = { + env: { OS: OperatingSystem.Windows, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'ControlLeft', ctrlKey: false, isComposing: false, key: 'Control', keyCode: 17, location: 1, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1561.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyG', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1566.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 1566.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ㅎ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1566.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ㅎ' }, + { timeStamp: 1567.00, state: { value: 'aaㅎaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'ㅎ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1681.00, state: { value: 'aaㅎaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyG', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1681.00, state: { value: 'aaㅎaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyG', ctrlKey: false, isComposing: true, key: 'g', keyCode: 71, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2013.00, state: { value: 'aaㅎaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2013.00, state: { value: 'aaㅎaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '하', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2013.00, state: { value: 'aaㅎaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '하' }, + { timeStamp: 2013.00, state: { value: 'aa하aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '하', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2096.00, state: { value: 'aa하aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2096.00, state: { value: 'aa하aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'k', keyCode: 75, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2457.00, state: { value: 'aa하aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2457.00, state: { value: 'aa하aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '한', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2457.00, state: { value: 'aa하aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '한' }, + { timeStamp: 2457.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '한', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2568.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2568.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyS', ctrlKey: false, isComposing: true, key: 's', keyCode: 83, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3066.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3066.00, state: { value: 'aa한aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '한', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3066.00, state: { value: 'aa한aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '한' }, + { timeStamp: 3066.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '한', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3066.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionend', data: '한' }, + { timeStamp: 3070.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 3070.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ㄱ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3070.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ㄱ' }, + { timeStamp: 3071.00, state: { value: 'aa한ㄱaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'ㄱ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3180.00, state: { value: 'aa한ㄱaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3180.00, state: { value: 'aa한ㄱaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'r', keyCode: 82, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3650.00, state: { value: 'aa한ㄱaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyM', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3650.00, state: { value: 'aa한ㄱaa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: '그', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3650.00, state: { value: 'aa한ㄱaa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: '그' }, + { timeStamp: 3650.00, state: { value: 'aa한그aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '그', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 3753.00, state: { value: 'aa한그aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyM', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 3768.00, state: { value: 'aa한그aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyM', ctrlKey: false, isComposing: true, key: 'm', keyCode: 77, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 4554.00, state: { value: 'aa한그aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyF', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 4554.00, state: { value: 'aa한그aa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: '글', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 4554.00, state: { value: 'aa한그aa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: '글' }, + { timeStamp: 4558.00, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '글', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 4685.00, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyF', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 4685.00, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyF', ctrlKey: false, isComposing: true, key: 'f', keyCode: 70, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 6632.00, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionend', data: '글' }, + { timeStamp: 6634.00, state: { value: 'aa한글aa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: null, inputType: 'deleteContentBackward', isComposing: false }, + { timeStamp: 6634.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: null, inputType: 'deleteContentBackward', isComposing: false }, + { timeStamp: 6634.00, state: { value: 'aa한aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '글', inputType: 'insertText', isComposing: false }, + { timeStamp: 6634.00, state: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '글', inputType: 'insertText', isComposing: false } + ], + final: { value: 'aa한글aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, + + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㅎ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㅎ' }, + { type: 'type', text: '하', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '하' }, + { type: 'type', text: '한', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '한' }, + { type: 'type', text: '한', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '한' }, + { type: 'type', text: '한', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㄱ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㄱ' }, + { type: 'type', text: '그', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '그' }, + { type: 'type', text: '글', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '글' }, + { type: 'type', text: '글', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'type', text: '', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'type', text: '글', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + test('Windows - Chrome - Chinese', async () => { // Windows, Chinese, Type 'ni' press Space and then 'hao' and press Space. const recorded: IRecorded = { @@ -908,4 +1155,88 @@ suite('TextAreaInput', () => { assert.deepStrictEqual(actualResultingState, recorded.final); }); + test('Windows 11 - Chrome - Chinese', async () => { + // Windows, Chinese, Type 'ni' press Space and then 'hao' and press Space. + const recorded: IRecorded = { + env: { OS: OperatingSystem.Windows, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 1.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 'n', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'n' }, + { timeStamp: 1.00, state: { value: 'aanaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'n', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 63.00, state: { value: 'aanaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 63.00, state: { value: 'aanaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyN', ctrlKey: false, isComposing: true, key: 'n', keyCode: 78, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 331.00, state: { value: 'aanaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 331.00, state: { value: 'aanaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ni', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 331.00, state: { value: 'aanaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ni' }, + { timeStamp: 342.00, state: { value: 'aaniaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'ni', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 403.00, state: { value: 'aaniaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 403.00, state: { value: 'aaniaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyI', ctrlKey: false, isComposing: true, key: 'i', keyCode: 73, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 614.00, state: { value: 'aaniaa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 617.00, state: { value: 'aaniaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: '你', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 617.00, state: { value: 'aaniaa', selectionStart: 2, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: '你' }, + { timeStamp: 657.00, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '你', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 658.00, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionend', data: '你' }, + { timeStamp: 715.00, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 715.00, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: ' ', keyCode: 32, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1117.00, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyH', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1117.00, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 1117.00, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: 'h', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1117.00, state: { value: 'aa你aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'h' }, + { timeStamp: 1117.00, state: { value: 'aa你haa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: 'h', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1199.00, state: { value: 'aa你haa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyH', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1199.00, state: { value: 'aa你haa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyH', ctrlKey: false, isComposing: true, key: 'h', keyCode: 72, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1317.00, state: { value: 'aa你haa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyA', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1322.00, state: { value: 'aa你haa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ha', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1322.00, state: { value: 'aa你haa', selectionStart: 3, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ha' }, + { timeStamp: 1328.00, state: { value: 'aa你haaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'input', data: 'ha', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1419.00, state: { value: 'aa你haaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyA', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1419.00, state: { value: 'aa你haaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyA', ctrlKey: false, isComposing: true, key: 'a', keyCode: 65, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1592.00, state: { value: 'aa你haaa', selectionStart: 5, selectionEnd: 5, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1592.00, state: { value: 'aa你haaa', selectionStart: 3, selectionEnd: 5, selectionDirection: 'forward' }, type: 'beforeinput', data: 'hao', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1592.00, state: { value: 'aa你haaa', selectionStart: 3, selectionEnd: 5, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'hao' }, + { timeStamp: 1606.00, state: { value: 'aa你haoaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'input', data: 'hao', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1666.00, state: { value: 'aa你haoaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1681.00, state: { value: 'aa你haoaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyO', ctrlKey: false, isComposing: true, key: 'o', keyCode: 79, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2187.00, state: { value: 'aa你haoaa', selectionStart: 6, selectionEnd: 6, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: true, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2187.00, state: { value: 'aa你haoaa', selectionStart: 3, selectionEnd: 6, selectionDirection: 'forward' }, type: 'beforeinput', data: '好', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2187.00, state: { value: 'aa你haoaa', selectionStart: 3, selectionEnd: 6, selectionDirection: 'forward' }, type: 'compositionupdate', data: '好' }, + { timeStamp: 2199.00, state: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'input', data: '好', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 2199.00, state: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'compositionend', data: '好' }, + { timeStamp: 2315.00, state: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: 'Process', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 2323.00, state: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'Space', ctrlKey: false, isComposing: false, key: ' ', keyCode: 32, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { value: 'aa你好aa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'n' }, + { type: 'type', text: 'ni', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ni' }, + { type: 'type', text: '你', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '你' }, + { type: 'type', text: '你', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'h', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'h' }, + { type: 'type', text: 'ha', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ha' }, + { type: 'type', text: 'hao', replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'hao' }, + { type: 'type', text: '好', replacePrevCharCnt: 3, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '好' }, + { type: 'type', text: '好', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + }); From ff009a1e5041fccdc731e5f0257ca55498ceb59f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Nov 2021 22:13:01 +0100 Subject: [PATCH 0155/2210] remoteCli: Fix compile error --- src/vs/server/remoteCli.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/server/remoteCli.ts b/src/vs/server/remoteCli.ts index 35cc226a94f81..993184d89bc87 100644 --- a/src/vs/server/remoteCli.ts +++ b/src/vs/server/remoteCli.ts @@ -10,7 +10,7 @@ import * as _http from 'http'; import * as _os from 'os'; import { cwd } from 'vs/base/common/process'; import { dirname, extname, resolve, join } from 'vs/base/common/path'; -import { parseArgs, buildHelpMessage, buildVersionMessage, OPTIONS, OptionDescriptions } from 'vs/platform/environment/node/argv'; +import { parseArgs, buildHelpMessage, buildVersionMessage, OPTIONS, OptionDescriptions, ErrorReporter } from 'vs/platform/environment/node/argv'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; import { PipeCommand } from 'vs/workbench/api/node/extHostCLIServer'; @@ -105,13 +105,17 @@ export function main(desc: ProductDescription, args: string[]): void { options['openExternal'] = { type: 'boolean' }; } - const errorReporter = { + const errorReporter : ErrorReporter = { onMultipleValues: (id: string, usedValue: string) => { console.error(`Option ${id} can only be defined once. Using value ${usedValue}.`); }, onUnknownOption: (id: string) => { console.error(`Ignoring option ${id}: not supported for ${desc.executableName}.`); + }, + + onDeprecatedOption: (deprecatedOption: string, actualOption: string) => { + console.warn(`Option '${deprecatedOption}' is deprecated, please use '${actualOption}' instead`); } }; From 92b20d483e0b7fd53577862bb04c452e8b62198c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Nov 2021 22:21:30 +0100 Subject: [PATCH 0156/2210] html custom data: open external uris with openTextDocument. For #135459 --- .../client/src/customData.ts | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index af8652af2bcd6..80e5f2f04d981 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -9,42 +9,50 @@ import { Utils } from 'vscode-uri'; export function getCustomDataSource(runtime: Runtime, toDispose: Disposable[]) { - let pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); - let pathsInExtensions = getCustomDataPathsFromAllExtensions(); + let localExtensionUris = new Set(); + let externalExtensionUris = new Set(); + const workspaceUris = new Set(); + + collectInWorkspaces(workspaceUris); + collectInExtensions(localExtensionUris, externalExtensionUris); const onChange = new EventEmitter(); toDispose.push(extensions.onDidChange(_ => { - const newPathsInExtensions = getCustomDataPathsFromAllExtensions(); - if (pathsInExtensions.size !== newPathsInExtensions.size || ![...pathsInExtensions].every(path => newPathsInExtensions.has(path))) { - pathsInExtensions = newPathsInExtensions; + const newLocalExtensionUris = new Set(); + const newExternalExtensionUris = new Set(); + collectInExtensions(newLocalExtensionUris, newExternalExtensionUris); + if (hasChanges(newLocalExtensionUris, localExtensionUris) || hasChanges(newExternalExtensionUris, externalExtensionUris)) { + localExtensionUris = newLocalExtensionUris; + externalExtensionUris = newExternalExtensionUris; onChange.fire(); } })); toDispose.push(workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration('html.customData')) { - pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); + workspaceUris.clear(); + collectInWorkspaces(workspaceUris); onChange.fire(); } })); toDispose.push(workspace.onDidChangeTextDocument(e => { const path = e.document.uri.toString(); - if (pathsInExtensions.has(path) || pathsInWorkspace.has(path)) { + if (externalExtensionUris.has(path) || workspaceUris.has(path)) { onChange.fire(); } })); return { get uris() { - return [...pathsInWorkspace].concat([...pathsInExtensions]); + return [...localExtensionUris].concat([...externalExtensionUris], [...workspaceUris]); }, get onDidChange() { return onChange.event; }, getContent(uriString: string): Thenable { const uri = Uri.parse(uriString); - if (pathsInExtensions.has(uriString)) { + if (localExtensionUris.has(uriString)) { return workspace.fs.readFile(uri).then(buffer => { return new runtime.TextDecoder().decode(buffer); }); @@ -56,12 +64,24 @@ export function getCustomDataSource(runtime: Runtime, toDispose: Disposable[]) { }; } +function hasChanges(s1: Set, s2: Set) { + if (s1.size !== s2.size) { + return true; + } + for (const uri of s1) { + if (!s2.has(uri)) { + return true; + } + } + return false; +} + function isURI(uriOrPath: string) { return /^(?\w[\w\d+.-]*):/.test(uriOrPath); } -function getCustomDataPathsInAllWorkspaces(): Set { +function collectInWorkspaces(workspaceUris: Set): Set { const workspaceFolders = workspace.workspaceFolders; const dataPaths = new Set(); @@ -76,10 +96,10 @@ function getCustomDataPathsInAllWorkspaces(): Set { if (typeof uriOrPath === 'string') { if (!isURI(uriOrPath)) { // path in the workspace - dataPaths.add(Utils.resolvePath(rootFolder, uriOrPath).toString()); + workspaceUris.add(Utils.resolvePath(rootFolder, uriOrPath).toString()); } else { // external uri - dataPaths.add(uriOrPath); + workspaceUris.add(uriOrPath); } } } @@ -104,22 +124,20 @@ function getCustomDataPathsInAllWorkspaces(): Set { return dataPaths; } -function getCustomDataPathsFromAllExtensions(): Set { - const dataPaths = new Set(); +function collectInExtensions(localExtensionUris: Set, externalUris: Set): void { for (const extension of extensions.all) { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { for (const uriOrPath of customData) { if (!isURI(uriOrPath)) { // relative path in an extension - dataPaths.add(Uri.joinPath(extension.extensionUri, uriOrPath).toString()); + localExtensionUris.add(Uri.joinPath(extension.extensionUri, uriOrPath).toString()); } else { // external uri - dataPaths.add(uriOrPath); + externalUris.add(uriOrPath); } } } } - return dataPaths; } From 63b41474cf7590e2eed9f4c23ca61d614aea4e3d Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 29 Nov 2021 13:17:21 -0800 Subject: [PATCH 0157/2210] tweak output size limit wording --- .../notebook/browser/view/output/transforms/textHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts index e95eb01a05da8..20611c7337086 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts @@ -23,7 +23,7 @@ const SIZE_LIMIT = 65535; function generateViewMoreElement(notebookUri: URI, cellViewModel: IGenericCellViewModel, outputId: string, disposables: DisposableStore, openerService: IOpenerService): HTMLElement { const md: IMarkdownString = { - value: `Output exceeds [size limit](command:workbench.action.openSettings?["notebook.output.textLineLimit"]), open the full output data[ in a text editor](command:workbench.action.openLargeOutput?${outputId})`, + value: `Output exceeds the [size limit](command:workbench.action.openSettings?["notebook.output.textLineLimit"]). Open the full output data[ in a text editor](command:workbench.action.openLargeOutput?${outputId})`, isTrusted: true, supportThemeIcons: true }; From 0933acd4cc4cc2ecae84ae7efe14d1e57d8bbacf Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 29 Nov 2021 13:37:54 -0800 Subject: [PATCH 0158/2210] move to drawio. --- .../browser/{ => docs}/notebook.layout.md | 4 +- .../docs/viewport-rendering.drawio.svg | 521 ++++++++++++++++++ 2 files changed, 524 insertions(+), 1 deletion(-) rename src/vs/workbench/contrib/notebook/browser/{ => docs}/notebook.layout.md (98%) create mode 100644 src/vs/workbench/contrib/notebook/browser/docs/viewport-rendering.drawio.svg diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.layout.md b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md similarity index 98% rename from src/vs/workbench/contrib/notebook/browser/notebook.layout.md rename to src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md index 83601355275c0..0d347c408e81d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.layout.md +++ b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md @@ -61,7 +61,9 @@ export abstract class CellPart extends Disposable { } ``` -![render in the core](https://user-images.githubusercontent.com/876920/142806570-a477d315-40f3-4e0c-8079-f2867d5f3e88.png) +![render viewport](./viewport-rendering.drawio.svg) + + When the notebook document contains markdown cells or rich outputs, the workflow is a bit more complex and become asynchornously partially due to the fact the markdown and rich outputs are rendered in a separate webview/iframe. While the list view renders the cell/row, it will send requests to the webview for output rendering, the rendering result (like dimensions of the output elements) won't come back in current frame. Once we receive the output rendering results from the webview (say next frame), we would ask the list view to adjust the position/dimension of the cell and ones below. diff --git a/src/vs/workbench/contrib/notebook/browser/docs/viewport-rendering.drawio.svg b/src/vs/workbench/contrib/notebook/browser/docs/viewport-rendering.drawio.svg new file mode 100644 index 0000000000000..5145d713e8e79 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/docs/viewport-rendering.drawio.svg @@ -0,0 +1,521 @@ + + + + + + + + + + +
    +
    +
    + Render Viewport +
    +
    +
    +
    + + Render Viewport + +
    +
    + + + + +
    +
    +
    + Notebook List View +
    +
    +
    +
    + + Notebook List View + +
    +
    + + + + + + + + + +
    +
    +
    + Render Template +
    +
    +
    +
    + + Render Template + +
    +
    + + + + + + + + + +
    +
    +
    + Render Element +
    +
    +
    +
    + + Render Element + +
    +
    + + + + + +
    +
    +
    + Get Dynamic Height +
    +
    +
    +
    + + Get Dynamic Height + +
    +
    + + + + + + + + + +
    +
    +
    + Create Cell Templates/Parts +
    +
    +
    +
    + + Create Cell Templates/Parts + +
    +
    + + + + +
    +
    +
    + Toolbar +
    +
    +
    +
    + + Toolbar + +
    +
    + + + + +
    +
    +
    + Editor +
    +
    +
    +
    + + Editor + +
    +
    + + + + +
    +
    +
    + Statusbar +
    +
    +
    +
    + + Statusbar + +
    +
    + + + + + + + +
    +
    +
    + Code Cell +
    +
    +
    +
    + + Code Cell + +
    +
    + + + + + +
    +
    +
    + Render Cell Parts +
    +
    +
    +
    + + Render Cell Parts + +
    +
    + + + + + + + + + + + +
    +
    +
    + CellPart read DOM +
    +
    +
    +
    + + CellPart read DOM + +
    +
    + + + + +
    +
    +
    + Update layout info +
    +
    +
    +
    + + Update layout info + +
    +
    + + + + + + + +
    +
    +
    + Toolbar.renderCell +
    +
    +
    +
    + + Toolbar.renderCell + +
    +
    + + + + +
    +
    +
    + Toolbar.renderCell +
    +
    +
    +
    + + Toolbar.renderCell + +
    +
    + + + + +
    +
    +
    + Toolbar.renderCell +
    +
    +
    +
    + + Toolbar.renderCell + +
    +
    + + + + +
    +
    +
    + Toolbar.renderCell +
    +
    +
    +
    + + Toolbar.renderCell + +
    +
    + + + + +
    +
    +
    + Toolbar.renderCell +
    +
    +
    +
    + + Toolbar.renderCell + +
    +
    + + + + +
    +
    +
    + + Toolbar.prepareLayout + +
    +
    +
    +
    + + Toolbar.prepareLay... + +
    +
    + + + + + + + + + + + +
    +
    +
    + Cell Layout Change +
    +
    +
    +
    + + Cell Layout Change + +
    +
    + + + + + +
    +
    +
    + Cell Part updateLayout +
    +
    +
    +
    + + Cell Part updateLayout + +
    +
    + + + + +
    +
    +
    + Toolbar.renderCell +
    +
    +
    +
    + + Toolbar.renderCell + +
    +
    + + + + +
    +
    +
    + Toolbar.renderCell +
    +
    +
    +
    + + Toolbar.renderCell + +
    +
    + + + + +
    +
    +
    + + Toolbar.updateLayout + +
    +
    +
    +
    + + Toolbar.updateLayo... + +
    +
    + + + + +
    +
    +
    + Next Frame +
    +
    +
    +
    + + Next Frame + +
    +
    + + + + +
    +
    +
    + + DOM Read + +
    +
    +
    +
    + + DOM Read + +
    +
    + + + + +
    +
    +
    + + DOM Write + +
    +
    +
    +
    + + DOM Write + +
    +
    +
    + + + + + Viewer does not support full SVG 1.1 + + + +
    From 9fc5b78314ced25e8e8aad994314c989b421cd2e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 22:51:14 +0100 Subject: [PATCH 0159/2210] Try to get textarea's cursor to line up with our cursor horizontally (improves #136097) --- src/vs/editor/browser/controller/textAreaHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 62c7237daf6d5..a0893ff6dd33f 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -581,7 +581,7 @@ export class TextAreaHandler extends ViewPart { ); // In case the textarea contains a word, we're going to try to align the textarea's cursor // with our cursor by scrolling the textarea as much as possible - this.textArea.domNode.scrollLeft = 1000000; + this.textArea.domNode.scrollLeft = this._primaryCursorVisibleRange.left; return; } From 31fff5074d286d845d6de9e1bbc09aafa266a062 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 22:55:38 +0100 Subject: [PATCH 0160/2210] Fixes #138095: Do not touch the textarea during a `compositionstart` event --- src/vs/editor/browser/controller/textAreaHandler.ts | 10 ++++++++++ src/vs/editor/browser/controller/textAreaInput.ts | 1 - .../test/browser/controller/textAreaInput.test.ts | 10 ++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index a0893ff6dd33f..36dec6dfd97de 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -293,6 +293,11 @@ export class TextAreaHandler extends ViewPart { visibleRange.left, canUseZeroSizeTextarea ? 0 : 1 ); + // The textarea might contain more than just the currently composed text + // so we will scroll the textarea as much as possible to the left, which + // means that the browser will perfectly center the currently composed text + // when it scrolls to the right to reveal the textarea cursor. + this.textArea.domNode.scrollLeft = 0; this._render(); } @@ -309,6 +314,11 @@ export class TextAreaHandler extends ViewPart { } // adjust width by its size this._visibleTextArea = this._visibleTextArea.setWidth(measureText(e.data, this._fontInfo)); + // The textarea might contain more than just the currently composed text + // so we will scroll the textarea as much as possible to the left, which + // means that the browser will perfectly center the currently composed text + // when it scrolls to the right to reveal the textarea cursor. + this.textArea.domNode.scrollLeft = 0; this._render(); })); diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 262ac40cf44f2..48f131dd6ffc4 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -289,7 +289,6 @@ export class TextAreaInput extends Disposable { return; } - this._setAndWriteTextAreaState('compositionstart', TextAreaState.EMPTY); this._onCompositionStart.fire({ revealDeltaColumns: 0 }); })); diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index 144975d784686..8ef48b4992c13 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -86,6 +86,7 @@ suite('TextAreaInput', () => { readonly onSyntheticTap = Event.None; private _state: IRecordedTextareaState; + private _currDispatchingEvent: IRecordedEvent | null; constructor() { super(); @@ -95,6 +96,7 @@ suite('TextAreaInput', () => { selectionStart: 0, value: '' }; + this._currDispatchingEvent = null; } public _initialize(state: IRecordedTextareaState): void { @@ -104,6 +106,7 @@ suite('TextAreaInput', () => { } public _dispatchRecordedEvent(event: IRecordedEvent): void { + this._currDispatchingEvent = event; this._state.value = event.state.value; this._state.selectionStart = event.state.selectionStart; this._state.selectionEnd = event.state.selectionEnd; @@ -161,12 +164,16 @@ suite('TextAreaInput', () => { } else { throw new Error(`Not Implemented`); } + this._currDispatchingEvent = null; } getValue(): string { return this._state.value; } setValue(reason: string, value: string): void { + if (this._currDispatchingEvent?.type === 'compositionstart') { + assert.fail('should not change the state of the textarea in a compositionstart'); + } this._state.value = value; } getSelectionStart(): number { @@ -176,6 +183,9 @@ suite('TextAreaInput', () => { return this._state.selectionDirection === 'backward' ? this._state.selectionStart : this._state.selectionEnd; } setSelectionRange(reason: string, selectionStart: number, selectionEnd: number): void { + if (this._currDispatchingEvent?.type === 'compositionstart') { + assert.fail('should not change the state of the textarea in a compositionstart'); + } this._state.selectionStart = selectionStart; this._state.selectionEnd = selectionEnd; this._state.selectionDirection = (selectionStart !== selectionEnd ? 'forward' : 'none'); From 06f34325ad78705cafaeec1ced2ebdca26d6f45a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 29 Nov 2021 14:26:23 -0800 Subject: [PATCH 0161/2210] Fix #125490 --- .../contrib/notebook/browser/view/cellParts/cellOutput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts index 2590f1251b108..fd7cd756217cb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts @@ -398,7 +398,7 @@ export class CellOutputElement extends Disposable { }; // TODO: This could probably be a real registered action, but it has to talk to this output element - const pickAction = new Action('notebook.output.pickMimetype', nls.localize('pickMimeType', "Choose Output Mimetype"), ThemeIcon.asClassName(mimetypeIcon), undefined, + const pickAction = new Action('notebook.output.pickMimetype', nls.localize('pickMimeType', "Change Presentation"), ThemeIcon.asClassName(mimetypeIcon), undefined, async _context => this._pickActiveMimeTypeRenderer(outputItemDiv, notebookTextModel, kernel, this.output)); if (index === 0 && useConsolidatedButton) { const menu = this._renderDisposableStore.add(this.menuService.createMenu(MenuId.NotebookOutputToolbar, this.contextKeyService)); From cd34f2369973d80a98908607e53cb21c83c86298 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 29 Nov 2021 23:28:42 +0100 Subject: [PATCH 0162/2210] improve json cache log messages (for #101050) --- .../client/src/node/jsonClientMain.ts | 16 +++++--- .../client/src/node/schemaCache.ts | 40 ++++++++++++++----- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index 186e7c8f7bfb7..fd8b34ad355fc 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -71,6 +71,7 @@ async function getPackageInfo(context: ExtensionContext): Promise interface Log { trace(message: string): void; + isTrace(): boolean; dispose(): void; } @@ -88,12 +89,14 @@ function getLog(outputChannel: OutputChannel): Log { outputChannel.appendLine(message); } }, + isTrace() { + return trace; + }, dispose: () => configListener.dispose() }; } -const retryTimeoutInDays = 2; // 2 days -const retryTimeoutInMs = retryTimeoutInDays * 24 * 60 * 60 * 1000; +const retryTimeoutInHours = 2 * 24; // 2 days async function getSchemaRequestService(context: ExtensionContext, log: Log): Promise { let cache: JSONSchemaCache | undefined = undefined; @@ -133,7 +136,7 @@ async function getSchemaRequestService(context: ExtensionContext, log: Log): Pro log.trace(`[json schema cache] Response: schema ${uri} unchanged etag ${etag}`); - const content = await cache.getSchema(uri, etag); + const content = await cache.getSchema(uri, etag, true); if (content) { log.trace(`[json schema cache] Get schema ${uri} etag ${etag} from cache`); return content; @@ -159,9 +162,12 @@ async function getSchemaRequestService(context: ExtensionContext, log: Log): Pro return { getContent: async (uri: string) => { if (cache && /^https?:\/\/json\.schemastore\.org\//.test(uri)) { - const content = await cache.getSchemaIfAccessedSince(uri, retryTimeoutInMs); + const content = await cache.getSchemaIfUpdatedSince(uri, retryTimeoutInHours); if (content) { - log.trace(`[json schema cache] Schema ${uri} from cache without request (last accessed less than ${retryTimeoutInDays} days ago)`); + if (log.isTrace()) { + log.trace(`[json schema cache] Schema ${uri} from cache without request (last accessed ${cache.getLastUpdatedInHours(uri)} hours ago)`); + } + return content; } } diff --git a/extensions/json-language-features/client/src/node/schemaCache.ts b/extensions/json-language-features/client/src/node/schemaCache.ts index 4661c5c7a1219..7f44498603d99 100644 --- a/extensions/json-language-features/client/src/node/schemaCache.ts +++ b/extensions/json-language-features/client/src/node/schemaCache.ts @@ -11,7 +11,7 @@ import { Memento } from 'vscode'; interface CacheEntry { etag: string; fileName: string; - accessTime: number; + updateTime: number; } interface CacheInfo { @@ -24,18 +24,34 @@ export class JSONSchemaCache { private readonly cacheInfo: CacheInfo; constructor(private readonly schemaCacheLocation: string, private readonly globalState: Memento) { - this.cacheInfo = globalState.get(MEMENTO_KEY, {}); + const infos = globalState.get(MEMENTO_KEY, {}) as CacheInfo; + const validated: CacheInfo = {}; + for (const schemaUri in infos) { + const { etag, fileName, updateTime } = infos[schemaUri]; + if (typeof etag === 'string' && typeof fileName === 'string' && typeof updateTime === 'number') { + validated[schemaUri] = { etag, fileName, updateTime }; + } + } + this.cacheInfo = validated; } getETag(schemaUri: string): string | undefined { return this.cacheInfo[schemaUri]?.etag; } + getLastUpdatedInHours(schemaUri: string): number | undefined { + const updateTime = this.cacheInfo[schemaUri]?.updateTime; + if (updateTime !== undefined) { + return (new Date().getTime() - updateTime) / 1000 / 60 / 60; + } + return undefined; + } + async putSchema(schemaUri: string, etag: string, schemaContent: string): Promise { try { const fileName = getCacheFileName(schemaUri); await fs.writeFile(path.join(this.schemaCacheLocation, fileName), schemaContent); - const entry: CacheEntry = { etag, fileName, accessTime: new Date().getTime() }; + const entry: CacheEntry = { etag, fileName, updateTime: new Date().getTime() }; this.cacheInfo[schemaUri] = entry; } catch (e) { delete this.cacheInfo[schemaUri]; @@ -44,19 +60,19 @@ export class JSONSchemaCache { } } - async getSchemaIfAccessedSince(schemaUri: string, expirationDuration: number): Promise { - const cacheEntry = this.cacheInfo[schemaUri]; - if (cacheEntry && cacheEntry.accessTime + expirationDuration >= new Date().getTime()) { - return this.loadSchemaFile(schemaUri, cacheEntry); + async getSchemaIfUpdatedSince(schemaUri: string, expirationDurationInHours: number): Promise { + const lastUpdatedInHours = this.getLastUpdatedInHours(schemaUri); + if (lastUpdatedInHours !== undefined && (lastUpdatedInHours < expirationDurationInHours)) { + return this.loadSchemaFile(schemaUri, this.cacheInfo[schemaUri], false); } return undefined; } - async getSchema(schemaUri: string, etag: string): Promise { + async getSchema(schemaUri: string, etag: string, etagValid: boolean): Promise { const cacheEntry = this.cacheInfo[schemaUri]; if (cacheEntry) { if (cacheEntry.etag === etag) { - return this.loadSchemaFile(schemaUri, cacheEntry); + return this.loadSchemaFile(schemaUri, cacheEntry, etagValid); } else { this.deleteSchemaFile(schemaUri, cacheEntry); } @@ -64,11 +80,13 @@ export class JSONSchemaCache { return undefined; } - private async loadSchemaFile(schemaUri: string, cacheEntry: CacheEntry): Promise { + private async loadSchemaFile(schemaUri: string, cacheEntry: CacheEntry, isUpdated: boolean): Promise { const cacheLocation = path.join(this.schemaCacheLocation, cacheEntry.fileName); try { const content = (await fs.readFile(cacheLocation)).toString(); - cacheEntry.accessTime = new Date().getTime(); + if (isUpdated) { + cacheEntry.updateTime = new Date().getTime(); + } return content; } catch (e) { delete this.cacheInfo[schemaUri]; From ee7a58bcfc4c9e285a335fab3f529257d7a40e65 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 29 Nov 2021 23:30:41 +0100 Subject: [PATCH 0163/2210] Add test for #119469 --- .../browser/controller/imeRecordedTypes.ts | 2 +- .../browser/controller/textAreaInput.test.ts | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/test/browser/controller/imeRecordedTypes.ts b/src/vs/editor/test/browser/controller/imeRecordedTypes.ts index 7c1677c6bd6f8..01224552e459b 100644 --- a/src/vs/editor/test/browser/controller/imeRecordedTypes.ts +++ b/src/vs/editor/test/browser/controller/imeRecordedTypes.ts @@ -43,7 +43,7 @@ export interface IRecordedInputEvent { type: 'beforeinput' | 'input'; data: string | null; inputType: string; - isComposing: boolean; + isComposing: boolean | undefined; } export type IRecordedEvent = IRecordedKeyboardEvent | IRecordedCompositionEvent | IRecordedInputEvent; diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index 8ef48b4992c13..481fd687f6d47 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -599,6 +599,49 @@ suite('TextAreaInput', () => { assert.deepStrictEqual(actualResultingState, recorded.final); }); + test('macOS - Safari - Chinese - issue #119469', async () => { + const recorded: IRecorded = { + env: { 'OS': OperatingSystem.Macintosh, 'browser': { 'isAndroid': false, 'isFirefox': false, 'isChrome': false, 'isSafari': true } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionstart', data: '' }, + { timeStamp: 1.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'compositionupdate', data: 'f' }, + { timeStamp: 1.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'beforeinput', data: 'f', inputType: 'insertCompositionText', isComposing: undefined }, + { timeStamp: 2.00, state: { value: 'aafaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'f', inputType: 'insertCompositionText', isComposing: undefined }, + { timeStamp: -30.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'KeyF', ctrlKey: false, isComposing: true, key: 'f', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 106.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyF', ctrlKey: false, isComposing: true, key: 'f', keyCode: 70, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 721.00, state: { value: 'aafaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: null, inputType: 'deleteCompositionText', isComposing: undefined }, + { timeStamp: 723.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'input', data: null, inputType: 'deleteCompositionText', isComposing: undefined }, + { timeStamp: 723.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'none' }, type: 'beforeinput', data: 'f', inputType: 'insertFromComposition', isComposing: undefined }, + { timeStamp: 723.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'input', data: 'f', inputType: 'insertFromComposition', isComposing: undefined }, + { timeStamp: 723.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'compositionend', data: 'f' }, + { timeStamp: 698.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Enter', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 826.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Enter', keyCode: 13, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1114.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keydown', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Enter', keyCode: 13, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1114.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'keypress', altKey: false, charCode: 13, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Enter', keyCode: 13, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1137.00, state: { value: 'aafaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'none' }, type: 'beforeinput', data: null, inputType: 'insertLineBreak', isComposing: undefined }, + { timeStamp: 1138.00, state: { value: 'aaf\naa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'input', data: null, inputType: 'insertLineBreak', isComposing: undefined }, + { timeStamp: 1250.00, state: { value: 'aaf\naa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' }, type: 'keyup', altKey: false, charCode: 0, code: 'Enter', ctrlKey: false, isComposing: false, key: 'Enter', keyCode: 13, location: 0, metaKey: false, repeat: false, shiftKey: false } + ], + final: { + value: 'aaf\naa', selectionStart: 4, selectionEnd: 4, selectionDirection: 'none' + }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, ([ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'f', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'f' }, + { type: 'type', text: 'f', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' }, + { type: 'type', text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 } + ])); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + test('Windows - Chrome - Japanese using Hiragana', async () => { // Windows, Japanese/Hiragana, type 'sennsei' and Enter const recorded: IRecorded = { From 37e1fac5820e57df968071a3a9a8aaa926e00031 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 29 Nov 2021 15:01:30 -0800 Subject: [PATCH 0164/2210] fix #137710 --- .../contrib/terminal/browser/terminalTabbedView.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index c7ed0495e464a..3b03cbcd714aa 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -283,9 +283,7 @@ export class TerminalTabbedView extends Disposable { } private _rerenderTabs() { - const hasText = this._tabListElement.clientWidth > TerminalTabsListSizes.MidpointViewWidth; - this._tabContainer.classList.toggle('has-text', hasText); - this._terminalIsTabsNarrowContextKey.set(!hasText); + this._updateHasText(); this._tabList.refresh(); } @@ -311,6 +309,12 @@ export class TerminalTabbedView extends Disposable { } } + private _updateHasText() { + const hasText = this._tabListElement.clientWidth > TerminalTabsListSizes.MidpointViewWidth; + this._tabContainer.classList.toggle('has-text', hasText); + this._terminalIsTabsNarrowContextKey.set(!hasText); + } + layout(width: number, height: number): void { this._height = height; this._width = width; @@ -318,6 +322,7 @@ export class TerminalTabbedView extends Disposable { if (this._shouldShowTabs()) { this._splitView.resizeView(this._tabTreeIndex, this._getLastListWidth()); } + this._updateHasText(); } private _updateTheme(theme?: IColorTheme): void { From 730e0da4ea122b04524f3cb99b3c790e3db22e61 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 29 Nov 2021 15:45:31 -0800 Subject: [PATCH 0165/2210] fix frequent refresh of available profiles (#138096) --- .../browser/terminalProfileService.ts | 35 +++++++++++++------ .../browser/terminalProfileService.test.ts | 9 +++-- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index 00f3aa099d68c..db78a9e0a7efe 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -34,6 +34,7 @@ export class TerminalProfileService implements ITerminalProfileService { private _availableProfiles: ITerminalProfile[] | undefined; private _contributedProfiles: IExtensionTerminalProfile[] = []; private _defaultProfileName?: string; + private _platformConfigJustRefreshed = false; private readonly _profileProviders: Map> = new Map(); private readonly _onDidChangeAvailableProfiles = new Emitter(); @@ -41,7 +42,9 @@ export class TerminalProfileService implements ITerminalProfileService { get profilesReady(): Promise { return this._profilesReadyBarrier.wait().then(() => { }); } get availableProfiles(): ITerminalProfile[] { - this.refreshAvailableProfiles(); + if (!this._platformConfigJustRefreshed) { + this.refreshAvailableProfiles(); + } return this._availableProfiles || []; } get contributedProfiles(): IExtensionTerminalProfile[] { @@ -60,14 +63,6 @@ export class TerminalProfileService implements ITerminalProfileService { // that contributes a profile this._extensionService.onDidChangeExtensions(() => this.refreshAvailableProfiles()); - this._configurationService.onDidChangeConfiguration(async e => { - const platformKey = await this.getPlatformKey(); - if (e.affectsConfiguration(TerminalSettingPrefix.DefaultProfile + platformKey) || - e.affectsConfiguration(TerminalSettingPrefix.Profiles + platformKey) || - e.affectsConfiguration(TerminalSettingId.UseWslProfiles)) { - this.refreshAvailableProfiles(); - } - }); this._webExtensionContributedProfileContextKey = TerminalContextKeys.webExtensionContributedProfile.bindTo(this._contextKeyService); this._updateWebContextKey(); // Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual @@ -75,6 +70,26 @@ export class TerminalProfileService implements ITerminalProfileService { // this long. this._profilesReadyBarrier = new AutoOpenBarrier(5000); this.refreshAvailableProfiles(); + this._setupConfigListener(); + } + + private async _setupConfigListener(): Promise { + const platformKey = await this.getPlatformKey(); + + this._configurationService.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration(TerminalSettingPrefix.DefaultProfile + platformKey) || + e.affectsConfiguration(TerminalSettingPrefix.Profiles + platformKey) || + e.affectsConfiguration(TerminalSettingId.UseWslProfiles)) { + if (e.source !== ConfigurationTarget.DEFAULT) { + // when _refreshPlatformConfig is called within refreshAvailableProfiles + // on did change configuration is fired. this can lead to an infinite recursion + this.refreshAvailableProfiles(); + this._platformConfigJustRefreshed = false; + } else { + this._platformConfigJustRefreshed = true; + } + } + }); } _serviceBrand: undefined; @@ -107,7 +122,7 @@ export class TerminalProfileService implements ITerminalProfileService { this._onDidChangeAvailableProfiles.fire(this._availableProfiles); this._profilesReadyBarrier.open(); this._updateWebContextKey(); - await this._refreshPlatformConfig(profiles); + await this._refreshPlatformConfig(this._availableProfiles); } } diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts index 7a92132511c1e..e79edc6f2d619 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts @@ -13,7 +13,7 @@ import { IExtensionTerminalProfile, ITerminalProfile } from 'vs/platform/termina import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { isLinux, isWindows, OperatingSystem } from 'vs/base/common/platform'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -213,7 +213,7 @@ suite('TerminalProfileService', () => { } } }); - configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); + configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, source: ConfigurationTarget.USER } as any); await terminalProfileService.refreshAndAwaitAvailableProfiles(); deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); deepStrictEqual(terminalProfileService.contributedProfiles, []); @@ -229,7 +229,7 @@ suite('TerminalProfileService', () => { } } }); - configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); + configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, source: ConfigurationTarget.USER } as any); await terminalProfileService.refreshAndAwaitAvailableProfiles(); deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); deepStrictEqual(terminalProfileService.contributedProfiles, []); @@ -245,7 +245,7 @@ suite('TerminalProfileService', () => { } } }); - configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); + configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, source: ConfigurationTarget.USER } as any); await terminalProfileService.refreshAndAwaitAvailableProfiles(); deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); deepStrictEqual(terminalProfileService.contributedProfiles, []); @@ -283,7 +283,6 @@ suite('TerminalProfileService', () => { } } }); - configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await terminalProfileService.hasRefreshedProfiles; deepStrictEqual(calls, [ [powershellProfile] From db95d3c179ed6cff7e56605ff226b49257942afd Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 29 Nov 2021 16:13:58 -0800 Subject: [PATCH 0166/2210] Fix #136811 --- src/vs/workbench/contrib/notebook/browser/notebookEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 67683ca4f6e97..d85e091b773be 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -103,7 +103,7 @@ export class NotebookEditor extends EditorPane { return this._widget.value?.textModel; } - override get minimumWidth(): number { return 375; } + override get minimumWidth(): number { return 220; } override get maximumWidth(): number { return Number.POSITIVE_INFINITY; } // these setters need to exist because this extends from EditorPane From 5300aa4054d352fea3345554a52a94df1397c744 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Tue, 30 Nov 2021 10:55:04 +0900 Subject: [PATCH 0167/2210] Run OSS tool --- ThirdPartyNotices.txt | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 04fcf3671b517..95e82140a9a78 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -7,7 +7,7 @@ This project incorporates components from the projects listed below. The origina 1. atom/language-clojure version 0.22.7 (https://github.com/atom/language-clojure) 2. atom/language-coffee-script version 0.49.3 (https://github.com/atom/language-coffee-script) -3. atom/language-css version 0.44.6 (https://github.com/atom/language-css) +3. atom/language-css version 0.45.0 (https://github.com/atom/language-css) 4. atom/language-java version 0.32.1 (https://github.com/atom/language-java) 5. atom/language-sass version 0.62.1 (https://github.com/atom/language-sass) 6. atom/language-shellscript version 0.26.0 (https://github.com/atom/language-shellscript) @@ -26,7 +26,7 @@ This project incorporates components from the projects listed below. The origina 19. fadeevab/make.tmbundle (https://github.com/fadeevab/make.tmbundle) 20. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift) 21. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/) -22. Ikuyadeu/vscode-R version 2.1.0 (https://github.com/Ikuyadeu/vscode-R) +22. Ikuyadeu/vscode-R version 2.3.2 (https://github.com/Ikuyadeu/vscode-R) 23. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) 24. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) 25. James-Yu/LaTeX-Workshop version 8.19.1 (https://github.com/James-Yu/LaTeX-Workshop) diff --git a/package.json b/package.json index 29e6125949068..f46c6eaa2faa7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.63.0", - "distro": "1aa3ab55b3cceca22ca6d647dc0095d562d23c8d", + "distro": "d2c899d07c70f2580da86cebee758d8a3ff136d5", "author": { "name": "Microsoft Corporation" }, From b9cf83f70bb6b4697b544a97c7056dfd28fccc5f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 29 Nov 2021 17:46:12 -0800 Subject: [PATCH 0168/2210] Show raw cell language ID when an extension is not installed for that language Fix #136051 --- .../browser/contrib/cellStatusBar/statusBarProviders.ts | 2 +- .../contrib/notebook/common/model/notebookCellTextModel.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts index a59607ea9554f..8d7db31e8e862 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts @@ -36,7 +36,7 @@ class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemP const languageId = cell.cellKind === CellKind.Markup ? 'markdown' : (this._modeService.getModeIdForLanguageName(cell.language) || cell.language); - const text = this._modeService.getLanguageName(languageId) || this._modeService.getLanguageName('plaintext'); + const text = this._modeService.getLanguageName(languageId) || languageId; const item = { text, command: CHANGE_CELL_LANGUAGE, diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 2bbd738aab698..dff3b9cb2f314 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -166,9 +166,9 @@ export class NotebookCellTextModel extends Disposable implements ICell { this._textModel = m; if (this._textModel) { // Init language from text model - // The language defined in the cell might not be supported in the editor so the text model might be using the default fallback (plaintext) + // The language defined in the cell might not be supported in the editor so the text model might be using the default fallback // If so let's not modify the language - if (!(this._modeService.getModeId(this.language) === null && this._textModel.getLanguageId() === 'plaintext')) { + if (!(this._modeService.getModeId(this.language) === null && (this._textModel.getLanguageId() === 'plaintext' || this._textModel.getLanguageId() === 'jupyter'))) { this.language = this._textModel.getLanguageId(); } From 8ce7d13c88d6cfde3d804268eca0de825d5ca365 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 30 Nov 2021 09:40:14 +0100 Subject: [PATCH 0169/2210] watcher - add test for atomic writes --- .../test/node/recursiveWatcher.integrationTest.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts b/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts index 0ea727f1bcd22..ae61017ed1fa9 100644 --- a/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts @@ -115,7 +115,7 @@ flakySuite('Recursive Watcher (parcel)', () => { if (failOnEventReason) { reject(new Error(`Unexpected file event: ${failOnEventReason}`)); } else { - resolve(); + setImmediate(() => resolve()); // copied from parcel watcher tests, seems to drop unrelated events on macOS } break; } @@ -272,6 +272,17 @@ flakySuite('Recursive Watcher (parcel)', () => { await changeFuture; }); + (isMacintosh /* this test seems not possible with fsevents backend */ ? test.skip : test)('basics (atomic writes)', async function () { + await service.watch([{ path: testDir, excludes: [] }]); + + // Delete + Recreate file + const newFilePath = join(testDir, 'deep', 'conway.js'); + let changeFuture: Promise = awaitEvent(service, newFilePath, FileChangeType.UPDATED); + await Promises.unlink(newFilePath); + Promises.writeFile(newFilePath, 'Hello Atomic World'); + await changeFuture; + }); + (!isLinux /* polling is only used in linux environments (WSL) */ ? test.skip : test)('basics (polling)', async function () { await service.watch([{ path: testDir, excludes: [], pollingInterval: 100 }]); From bb0045c54f431faee661a4d5590a044097938e63 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 30 Nov 2021 10:23:37 +0100 Subject: [PATCH 0170/2210] refactor - extract metadata type and use it in scanner --- .../common/abstractExtensionManagementService.ts | 2 -- .../common/extensionManagement.ts | 1 + .../node/extensionManagementService.ts | 4 ++-- .../extensionManagement/node/extensionsScanner.ts | 3 +-- .../common/webExtensionManagementService.ts | 4 ++-- .../common/webExtensionsScannerService.ts | 15 +++++++-------- 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 5fb80e314b110..3980f8b3a682a 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -23,8 +23,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export type Metadata = Partial; - export interface IInstallExtensionTask { readonly identifier: IExtensionIdentifier; readonly source: IGalleryExtension | URI; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 98814f6d6afdb..e3b8cb092d7d9 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -388,6 +388,7 @@ export class ExtensionManagementError extends Error { } } +export type Metadata = Partial; export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean, installPreReleaseVersion?: boolean }; export type InstallVSIXOptions = Omit & { installOnlyNewlyAddedFromExtensionPack?: boolean }; export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean }; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 0eb79df9a4fe4..0bb7d16806a50 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -19,10 +19,10 @@ import { IFile, zip } from 'vs/base/node/zip'; import * as nls from 'vs/nls'; import { IDownloadService } from 'vs/platform/download/common/download'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, joinErrors, Metadata, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; +import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, joinErrors, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; import { ExtensionManagementError, ExtensionManagementErrorCode, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallOperation, InstallOptions, - InstallVSIXOptions, TargetPlatform + InstallVSIXOptions, Metadata, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader'; diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index a11c6aa1d27f2..981ce6e662d59 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -19,8 +19,7 @@ import * as pfs from 'vs/base/node/pfs'; import { extract, ExtractError } from 'vs/base/node/zip'; import { localize } from 'vs/nls'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { Metadata } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; -import { ExtensionManagementError, ExtensionManagementErrorCode, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementError, ExtensionManagementErrorCode, Metadata, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; import { ExtensionType, IExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 1ea8f90cc6cb5..d72873f5c5f10 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionType, IExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions, Metadata, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, Metadata, IUninstallExtensionTask, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; +import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IProductService } from 'vs/platform/product/common/productService'; diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index 05a29726e1dff..e006eab5ed354 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -15,7 +15,7 @@ import { Queue } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IExtensionGalleryService, IGalleryExtension, IGalleryMetadata, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IGalleryExtension, IGalleryMetadata, Metadata, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Disposable } from 'vs/base/common/lifecycle'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; @@ -25,7 +25,6 @@ import { isString } from 'vs/base/common/types'; import { getErrorMessage } from 'vs/base/common/errors'; import { ResourceMap } from 'vs/base/common/map'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; -import { IStringDictionary } from 'vs/base/common/collections'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { CATEGORIES } from 'vs/workbench/common/actions'; @@ -41,7 +40,7 @@ interface IStoredWebExtension { readonly readmeUri?: UriComponents; readonly changelogUri?: UriComponents; readonly packageNLSUri?: UriComponents; - readonly metadata?: IStringDictionary; + readonly metadata?: Metadata; } interface IWebExtension { @@ -51,7 +50,7 @@ interface IWebExtension { readmeUri?: URI; changelogUri?: URI; packageNLSUri?: URI; - metadata?: IStringDictionary; + metadata?: Metadata; } export class WebExtensionsScannerService extends Disposable implements IWebExtensionsScannerService { @@ -260,12 +259,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return null; } - async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: IStringDictionary): Promise { + async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Metadata): Promise { const webExtension = await this.toWebExtensionFromGallery(galleryExtension, metadata); return this.addWebExtension(webExtension); } - async addExtension(location: URI, metadata?: IStringDictionary): Promise { + async addExtension(location: URI, metadata?: Metadata): Promise { const webExtension = await this.toWebExtension(location, undefined, undefined, undefined, undefined, metadata); return this.addWebExtension(webExtension); } @@ -330,7 +329,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return extensions; } - private async toWebExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: IStringDictionary): Promise { + private async toWebExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Metadata): Promise { let extensionLocation = this.extensionResourceLoaderService.getExtensionGalleryResourceURL(galleryExtension, 'extension'); if (!extensionLocation) { throw new Error('No extension gallery service configured.'); @@ -341,7 +340,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return this.toWebExtension(extensionLocation, galleryExtension.identifier, packageNLSResource ? URI.parse(packageNLSResource) : null, galleryExtension.assets.readme ? URI.parse(galleryExtension.assets.readme.uri) : undefined, galleryExtension.assets.changelog ? URI.parse(galleryExtension.assets.changelog.uri) : undefined, metadata); } - private async toWebExtension(extensionLocation: URI, identifier?: IExtensionIdentifier, packageNLSUri?: URI | null, readmeUri?: URI, changelogUri?: URI, metadata?: IStringDictionary): Promise { + private async toWebExtension(extensionLocation: URI, identifier?: IExtensionIdentifier, packageNLSUri?: URI | null, readmeUri?: URI, changelogUri?: URI, metadata?: Metadata): Promise { let packageJSONContent; try { packageJSONContent = await this.extensionResourceLoaderService.readExtensionResource(joinPath(extensionLocation, 'package.json')); From 7b6c1065c76708d146844154c8a33906f06dce0b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 30 Nov 2021 11:04:39 +0100 Subject: [PATCH 0171/2210] smoke - temporary bring back timeout --- test/smoke/package.json | 1 - .../areas/workbench/data-migration.test.ts | 46 ++----------------- test/smoke/yarn.lock | 12 ----- 3 files changed, 3 insertions(+), 56 deletions(-) diff --git a/test/smoke/package.json b/test/smoke/package.json index f134a185489ab..60292ae8c6ce8 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -14,7 +14,6 @@ "mkdirp": "^1.0.4", "ncp": "^2.0.0", "node-fetch": "^2.6.1", - "readdirp": "^3.6.0", "rimraf": "3.0.2", "vscode-test": "^1.6.1" }, diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 0c7f3c3d3ba39..518946c6469dc 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -5,10 +5,7 @@ import { Application, ApplicationOptions, Quality } from '../../../../automation'; import { ParsedArgs } from 'minimist'; -import * as readdirp from 'readdirp'; -import { afterSuite, getRandomUserDataDir, startApp } from '../../utils'; -import { join } from 'path'; -import { readFileSync, statSync } from 'fs'; +import { afterSuite, getRandomUserDataDir, startApp, timeout } from '../../utils'; export function setup(opts: ParsedArgs) { @@ -158,54 +155,17 @@ export function setup(opts: ParsedArgs) { await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType); await stableApp.workbench.editors.waitForTab(readmeMd, true); + await timeout(2000); // TODO@bpasero https://github.com/microsoft/vscode/issues/138055 + await stableApp.stop(); stableApp = undefined; - const backupsHome = join(userDataDir, 'Backups'); - console.log('Printing backup contents (after stable app stopped):'); - for await (const entry of readdirp(backupsHome)) { - if (entry.path === 'workspaces.json') { - continue; - } - try { - const contents = readFileSync(join(backupsHome, entry.path)).toString(); - const firstLine = contents.substring(0, contents.indexOf('\n')); - console.log(`${entry.path}: ${firstLine}`); - if (firstLine.length < 3) { - const stat = statSync(join(backupsHome, entry.path)); - console.log(`Unexpected short backup first line, size: ${stat.size}, full contents:`); - console.log(contents); - } - } catch (error) { - console.log(`${entry.path}: Error reading file: ${error}`); - } - } - const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); insiderOptions.userDataDir = userDataDir; insidersApp = new Application(insiderOptions); await insidersApp.start(); - console.log('Printing backup contents (after insiders app started):'); - for await (const entry of readdirp(backupsHome)) { - if (entry.path === 'workspaces.json') { - continue; - } - try { - const contents = readFileSync(join(backupsHome, entry.path)).toString(); - const firstLine = contents.substring(0, contents.indexOf('\n')); - console.log(`${entry.path}: ${firstLine}`); - if (firstLine.length < 3) { - const stat = statSync(join(backupsHome, entry.path)); - console.log(`Unexpected short backup first line, size: ${stat.size}, full contents:`); - console.log(contents); - } - } catch (error) { - console.log(`${entry.path}: Error reading file: ${error}`); - } - } - await insidersApp.workbench.editors.waitForTab(readmeMd, true); await insidersApp.workbench.quickaccess.openFile(readmeMd); await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index fe6bf509eea90..28bb27266551d 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -603,11 +603,6 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" -picomatch@^2.2.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - pidtree@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" @@ -645,13 +640,6 @@ readable-stream@^2.0.2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readdirp@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - resolve@^1.10.0: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" From 827f76521d33a61c19e92160649a0a6af8bad132 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 30 Nov 2021 12:32:51 +0100 Subject: [PATCH 0172/2210] Fix #138127 --- .../extensions/browser/extensionEditor.ts | 2 +- .../browser/extensions.contribution.ts | 8 +-- .../extensions/browser/extensionsActions.ts | 2 +- .../extensions/browser/extensionsWidgets.ts | 6 ++ .../browser/media/extensionEditor.css | 2 +- .../common/webExtensionsScannerService.ts | 62 ++++++++++++++----- src/vs/workbench/workbench.web.api.ts | 10 +-- 7 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 5c7a2f57a359b..82cd534445287 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -241,9 +241,9 @@ export class ExtensionEditor extends EditorPane { const preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); preview.textContent = localize('preview', "Preview"); + const preRelease = append(title, $('span.pre-release')); const builtin = append(title, $('span.builtin')); builtin.textContent = localize('builtin', "Built-in"); - const preRelease = append(title, $('span.pre-release')); const subtitle = append(details, $('.subtitle')); const publisher = append(append(subtitle, $('.subtitle-entry')), $('.publisher.clickable', { title: localize('publisher', "Publisher"), tabIndex: 0 })); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index c114661a85a11..d5b4c93d3fb00 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1172,7 +1172,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: '0_install', order: 0, - when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('showPreReleaseVersion')) + when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); @@ -1187,7 +1187,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: '0_install', order: 1, - when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('showPreReleaseVersion')) + when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); @@ -1202,7 +1202,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: '0_install', order: 2, - when: ContextKeyExpr.and(ContextKeyExpr.not('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed')) + when: ContextKeyExpr.and(ContextKeyExpr.not('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); @@ -1219,7 +1219,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: '0_install', order: 3, - when: ContextKeyExpr.and(ContextKeyExpr.has('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed')) + when: ContextKeyExpr.and(ContextKeyExpr.has('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 1066603a00030..884b922a9b228 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1089,7 +1089,7 @@ export class SwitchToPreReleaseVersionAction extends ExtensionAction { } update(): void { - this.enabled = !!this.extension && !this.extension.local?.isPreReleaseVersion && this.extension.hasPreReleaseVersion && this.extension.state === ExtensionState.Installed; + this.enabled = !!this.extension && !this.extension.isBuiltin && !this.extension.local?.isPreReleaseVersion && this.extension.hasPreReleaseVersion && this.extension.state === ExtensionState.Installed; } override async run(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index f18ce4af2670c..d96ecff7da9a9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -253,6 +253,9 @@ export class PreReleaseBookmarkWidget extends ExtensionWidget { if (!this.extension) { return; } + if (this.extension.isBuiltin) { + return; + } if (this.extension.hasPreReleaseVersion) { this.element = append(this.parent, $('div.extension-bookmark')); const preRelease = append(this.element, $('.pre-release')); @@ -580,6 +583,9 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (!extension.hasPreReleaseVersion) { return undefined; } + if (extension.isBuiltin) { + return undefined; + } if (extension.local?.isPreReleaseVersion || extension.gallery?.properties.isPreReleaseVersion) { return undefined; } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index b16594bab0942..0819697e27ed5 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -88,7 +88,7 @@ margin-left: 10px; } -.extension-editor > .header > .details > .title > .pre-release { +.extension-editor > .header > .details > .title > .pre-release:not(:empty) { display: flex; font-size: 10px; margin-left: 10px; diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index e006eab5ed354..f7f1dfbb8c821 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -32,6 +32,7 @@ import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { basename } from 'vs/base/common/path'; +import { flatten } from 'vs/base/common/arrays'; interface IStoredWebExtension { readonly identifier: IExtensionIdentifier; @@ -58,7 +59,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten declare readonly _serviceBrand: undefined; private readonly builtinExtensionsPromise: Promise = Promise.resolve([]); - private readonly cutomBuiltinExtensions: (string | URI)[]; + private readonly cutomBuiltinExtensions: ({ id: string, preRelease?: boolean } | URI)[]; private readonly customBuiltinExtensionsPromise: Promise = Promise.resolve([]); private readonly customBuiltinExtensionsCacheResource: URI | undefined = undefined; @@ -75,7 +76,9 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, ) { super(); - this.cutomBuiltinExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.additionalBuiltinExtensions) ? this.environmentService.options.additionalBuiltinExtensions : []; + this.cutomBuiltinExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.additionalBuiltinExtensions) + ? this.environmentService.options.additionalBuiltinExtensions.map(additionalBuiltinExtension => isString(additionalBuiltinExtension) ? { id: additionalBuiltinExtension } : additionalBuiltinExtension) + : []; if (isWeb) { this.installedExtensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json'); this.customBuiltinExtensionsCacheResource = joinPath(environmentService.userRoamingDataHome, 'customBuiltinExtensionsCache.json'); @@ -96,12 +99,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten * All extensions defined via `additionalBuiltinExtensions` API */ private async readCustomBuiltinExtensions(): Promise { - const extensionIds: string[] = [], extensionLocations: URI[] = [], result: IExtension[] = []; + const extensions: { id: string, preRelease: boolean }[] = [], extensionLocations: URI[] = [], result: IExtension[] = []; for (const e of this.cutomBuiltinExtensions) { - if (isString(e)) { - extensionIds.push(e); - } else { + if (URI.isUri(e)) { extensionLocations.push(URI.revive(e)); + } else { + extensions.push({ id: e.id, preRelease: !!e.preRelease }); } } @@ -119,11 +122,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } })(), (async () => { - if (extensionIds.length) { + if (extensions.length) { try { - result.push(...await this.getCustomBuiltinExtensionsFromGallery(extensionIds)); + result.push(...await this.getCustomBuiltinExtensionsFromGallery(extensions)); } catch (error) { - this.logService.info('Ignoring following additional builtin extensions as there is an error while fetching them from gallery', extensionIds, getErrorMessage(error)); + this.logService.info('Ignoring following additional builtin extensions as there is an error while fetching them from gallery', extensions.map(({ id }) => id), getErrorMessage(error)); } } else { await this.writeCustomBuiltinExtensionsCache(() => []); @@ -134,7 +137,26 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return result; } - private async getCustomBuiltinExtensionsFromGallery(extensionIds: string[]): Promise { + private async checkAdditionalBuiltinExtensions(extensions: { id: string, preRelease: boolean }[]): Promise<{ id: string, preRelease: boolean }[]> { + const extensionsControlManifest = await this.galleryService.getExtensionsControlManifest(); + const result: { id: string, preRelease: boolean }[] = []; + for (const extension of extensions) { + if (extensionsControlManifest.malicious.some(e => areSameExtensions(e, { id: extension.id }))) { + this.logService.info(`Checking additional builtin extensions: Ignoring '${extension.id}' because it is reported to be malicious.`); + continue; + } + if (extensionsControlManifest.unsupportedPreReleaseExtensions && extensionsControlManifest.unsupportedPreReleaseExtensions[extension.id.toLowerCase()]) { + const preReleaseExtensionId = extensionsControlManifest.unsupportedPreReleaseExtensions[extension.id.toLowerCase()].id; + this.logService.info(`Checking additional builtin extensions: '${extension.id}' is no longer supported, instead using '${preReleaseExtensionId}'`); + result.push({ id: preReleaseExtensionId, preRelease: true }); + } else { + result.push(extension); + } + } + return result; + } + + private async getCustomBuiltinExtensionsFromGallery(extensions: { id: string, preRelease: boolean }[]): Promise { if (!this.galleryService.isEnabled()) { this.logService.info('Ignoring fetching additional builtin extensions from gallery as it is disabled.'); return []; @@ -147,26 +169,32 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten cachedStaticWebExtensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]); const webExtensions: IWebExtension[] = []; - extensionIds = extensionIds.map(id => id.toLowerCase()); + extensions = extensions.map(e => ({ ...e, id: e.id.toLowerCase() })); for (const webExtension of cachedStaticWebExtensions) { - const index = extensionIds.indexOf(webExtension.identifier.id.toLowerCase()); + const index = extensions.findIndex(e => e.id === webExtension.identifier.id.toLowerCase() && e.preRelease === webExtension.metadata?.isPreReleaseVersion); if (index !== -1) { webExtensions.push(webExtension); - extensionIds.splice(index, 1); + extensions.splice(index, 1); } } - if (extensionIds.length) { - const galleryExtensions = await this.galleryService.getExtensions(extensionIds.map(id => ({ id })), CancellationToken.None); - const missingExtensions = extensionIds.filter(id => !galleryExtensions.find(({ identifier }) => areSameExtensions(identifier, { id }))); + if (extensions.length) { + extensions = (await this.checkAdditionalBuiltinExtensions(extensions)); + const preReleaseExtensions = extensions.filter(e => e.preRelease).map(({ id }) => ({ id })); + const releaseExtensions = extensions.filter(e => !e.preRelease).map(({ id }) => ({ id })); + const galleryExtensions = flatten(await Promise.all([ + preReleaseExtensions.length ? this.galleryService.getExtensions(preReleaseExtensions, true, CancellationToken.None) : Promise.resolve([]), + releaseExtensions.length ? this.galleryService.getExtensions(releaseExtensions, false, CancellationToken.None) : Promise.resolve([]), + ])); + const missingExtensions = extensions.filter(({ id }) => !galleryExtensions.find(({ identifier }) => areSameExtensions(identifier, { id }))); if (missingExtensions.length) { this.logService.info('Cannot find static extensions from gallery', missingExtensions); } await Promise.all(galleryExtensions.map(async gallery => { try { - webExtensions.push(await this.toWebExtensionFromGallery(gallery)); + webExtensions.push(await this.toWebExtensionFromGallery(gallery, { isPreReleaseVersion: gallery.properties.isPreReleaseVersion, isBuiltin: true })); } catch (error) { this.logService.info(`Ignoring additional builtin extension ${gallery.identifier.id} because there is an error while converting it into web extension`, getErrorMessage(error)); } diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 0185fdacf19a6..997ad303312f6 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -32,6 +32,8 @@ interface IResourceUriProvider { */ type ExtensionId = string; +type MarketplaceExtension = ExtensionId | { readonly id: ExtensionId, preRelease?: boolean }; + interface ICommonTelemetryPropertiesResolver { (): { [key: string]: any }; } @@ -431,12 +433,12 @@ interface IWorkbenchConstructionOptions { readonly credentialsProvider?: ICredentialsProvider; /** - * Additional builtin extensions that cannot be uninstalled but only be disabled. + * Additional builtin extensions those cannot be uninstalled but only be disabled. * It can be one of the following: - * - `ExtensionId`: id of the extension that is available in Marketplace - * - `UriComponents`: location of the extension where it is hosted. + * - an extension in the Marketplace + * - location of the extension where it is hosted. */ - readonly additionalBuiltinExtensions?: readonly (ExtensionId | UriComponents)[]; + readonly additionalBuiltinExtensions?: readonly (MarketplaceExtension | UriComponents)[]; /** * List of extensions to be enabled if they are installed. From 3febecac20f00eedbbcc13348ea27146f4d60eeb Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 30 Nov 2021 14:18:20 +0100 Subject: [PATCH 0173/2210] Make tree view specific visibility fix Fixes #136802 --- src/vs/workbench/browser/parts/views/treeView.ts | 14 +++++++++++++- .../workbench/browser/parts/views/viewsService.ts | 2 +- src/vs/workbench/common/views.ts | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 2655c4a7e975f..2da83d7dc2b89 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -63,6 +63,7 @@ import { Schemas } from 'vs/base/common/network'; export class TreeViewPane extends ViewPane { protected readonly treeView: ITreeView; + private _container: HTMLElement | undefined; constructor( options: IViewletViewOptions, @@ -82,7 +83,11 @@ export class TreeViewPane extends ViewPane { this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); this._register(this.treeView.onDidChangeTitle((newTitle) => this.updateTitle(newTitle))); this._register(this.treeView.onDidChangeDescription((newDescription) => this.updateTitleDescription(newDescription))); - this._register(toDisposable(() => this.treeView.setVisibility(false))); + this._register(toDisposable(() => { + if (this._container && this.treeView.container && (this._container === this.treeView.container)) { + this.treeView.setVisibility(false); + } + })); this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); this._register(this.treeView.onDidChangeWelcomeState(() => this._onDidChangeViewWelcomeState.fire())); if (options.title !== this.treeView.title) { @@ -101,6 +106,7 @@ export class TreeViewPane extends ViewPane { } override renderBody(container: HTMLElement): void { + this._container = container; super.renderBody(container); this.renderTreeView(container); } @@ -165,6 +171,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private tree: Tree | undefined; private treeLabels: ResourceLabels | undefined; private treeViewDnd: CustomTreeViewDragAndDrop; + private _container: HTMLElement | undefined; private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; @@ -466,6 +473,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { } show(container: HTMLElement): void { + this._container = container; DOM.append(container, this.domNode); } @@ -781,6 +789,10 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { this.domNode.removeAttribute('tabindex'); } } + + get container(): HTMLElement | undefined { + return this._container; + } } class TreeViewIdentityProvider implements IIdentityProvider { diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts index 6820de6a049b7..4ecd0454eb058 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -124,7 +124,7 @@ export class ViewsService extends Disposable implements IViewsService { private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void { this.deregisterPaneComposite(viewContainer, from); - setTimeout(() => this.registerPaneComposite(viewContainer, to), 0); + this.registerPaneComposite(viewContainer, to); } private onViewDescriptorsAdded(views: ReadonlyArray, container: ViewContainer): void { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index df43db9d31812..ce831f94d7671 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -682,6 +682,8 @@ export interface ITreeView extends IDisposable { readonly onDidChangeWelcomeState: Event; + readonly container: HTMLElement | undefined; + refresh(treeItems?: ITreeItem[]): Promise; setVisibility(visible: boolean): void; From 8587a5d837d4f9c8ede2f615ba9e974585a9eb6b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 30 Nov 2021 14:52:20 +0100 Subject: [PATCH 0174/2210] Fix layer breakage --- src/vs/workbench/common/views.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index ce831f94d7671..aa2cf21f1aeb5 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -682,7 +682,7 @@ export interface ITreeView extends IDisposable { readonly onDidChangeWelcomeState: Event; - readonly container: HTMLElement | undefined; + readonly container: any | undefined; refresh(treeItems?: ITreeItem[]): Promise; From 4ee3ed1ff239dd9b51cd07ed8a35e29ca9d441c2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 30 Nov 2021 14:56:42 +0100 Subject: [PATCH 0175/2210] Fix #138154 --- .../common/configurationModels.ts | 23 ++++++++++++---- .../common/configurationRegistry.ts | 1 + .../common/configurationService.ts | 6 ++--- .../test/common/configurationModels.test.ts | 6 ++--- .../api/common/configurationExtensionPoint.ts | 25 +----------------- .../configuration/browser/configuration.ts | 10 +++---- .../browser/configurationService.ts | 26 ++++++++++++++++--- .../test/browser/configuration.test.ts | 2 +- 8 files changed, 54 insertions(+), 45 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 0e7e1bff8107f..eb599ee7f30c4 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -595,12 +595,25 @@ export class Configuration { this._foldersConsolidatedConfigurations.delete(resource); } - compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel): IConfigurationChange { - const { added, updated, removed, overrides } = compare(this._defaultConfiguration, defaults); - const keys = [...added, ...updated, ...removed]; - if (keys.length) { - this.updateDefaultConfiguration(defaults); + compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel, keys?: string[]): IConfigurationChange { + const overrides: [string, string[]][] = []; + if (!keys) { + const { added, updated, removed } = compare(this._defaultConfiguration, defaults); + keys = [...added, ...updated, ...removed]; + } + for (const key of keys) { + for (const overrideIdentifier of overrideIdentifiersFromKey(key)) { + const fromKeys = this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier); + const toKeys = defaults.getKeysForOverrideIdentifier(overrideIdentifier); + const keys = [ + ...toKeys.filter(key => fromKeys.indexOf(key) === -1), + ...fromKeys.filter(key => toKeys.indexOf(key) === -1), + ...fromKeys.filter(key => !objects.equals(this._defaultConfiguration.override(overrideIdentifier).getValue(key), defaults.override(overrideIdentifier).getValue(key))) + ]; + overrides.push([overrideIdentifier, keys]); + } } + this.updateDefaultConfiguration(defaults); return { keys, overrides }; } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 1083115b16c62..28b6aa327ea51 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -215,6 +215,7 @@ export const windowSettings: { properties: IStringDictionary, patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage'; +export const configurationDefaultsSchemaId = 'vscode://schemas/settings/configurationDefaults'; const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 3bc96c77572fb..7860c72ea8e95 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -34,7 +34,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); - this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(() => this.onDidDefaultConfigurationChange())); + this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); this._register(this.userConfiguration.onDidChange(() => this.reloadConfigurationScheduler.schedule())); } @@ -89,9 +89,9 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.trigger(change, previous, ConfigurationTarget.USER); } - private onDidDefaultConfigurationChange(): void { + private onDidDefaultConfigurationChange(properties: string[]): void { const previous = this.configuration.toData(); - const change = this.configuration.compareAndUpdateDefaultConfiguration(new DefaultConfigurationModel()); + const change = this.configuration.compareAndUpdateDefaultConfiguration(new DefaultConfigurationModel(), properties); this.trigger(change, previous, ConfigurationTarget.DEFAULT); } diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 3b0b466cc55d1..ae1add24a7887 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -525,9 +525,9 @@ suite('Configuration', () => { '[markdown]': { 'editor.wordWrap': 'off' } - })); + }), ['editor.lineNumbers', '[markdown]']); - assert.deepStrictEqual(actual, { keys: ['[markdown]', 'editor.lineNumbers'], overrides: [['markdown', ['editor.wordWrap']]] }); + assert.deepStrictEqual(actual, { keys: ['editor.lineNumbers', '[markdown]'], overrides: [['markdown', ['editor.wordWrap']]] }); }); @@ -890,7 +890,7 @@ suite('ConfigurationChangeEvent', () => { '[markdown]': { 'editor.wordWrap': 'off' } - })), + }), ['editor.lineNumbers', '[markdown]']), configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ '[json]': { 'editor.lineNumbers': 'relative' diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index f1e7bff0d3520..dae0689cafe6c 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX, windowSettings, resourceSettings, machineOverridableSettings, IConfigurationDefaults } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_REGEX, IConfigurationDefaults, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject } from 'vs/base/common/types'; @@ -112,29 +112,6 @@ const configurationEntrySchema: IJSONSchema = { } }; -const configurationDefaultsSchemaId = 'vscode://schemas/settings/configurationDefaults'; -const configurationDefaultsSchema: IJSONSchema = { - type: 'object', - description: nls.localize('configurationDefaults.description', 'Contribute defaults for configurations'), - properties: {}, - patternProperties: { - [OVERRIDE_PROPERTY_PATTERN]: { - type: 'object', - default: {}, - $ref: resourceLanguageSettingsSchemaId, - } - }, - additionalProperties: false -}; -jsonRegistry.registerSchema(configurationDefaultsSchemaId, configurationDefaultsSchema); -configurationRegistry.onDidSchemaChange(() => { - configurationDefaultsSchema.properties = { - ...machineOverridableSettings.properties, - ...windowSettings.properties, - ...resourceSettings.properties - }; -}); - // BEGIN VSCode extension point `configurationDefaults` const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'configurationDefaults', diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index bc9ad5c1fdff5..b92fea9cdda4d 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -36,8 +36,8 @@ export class DefaultConfiguration extends Disposable { private cachedConfigurationDefaultsOverrides: IStringDictionary = {}; private readonly cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; - private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); - readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel, properties: string[] }>()); + readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; private updateCache: boolean = false; @@ -62,7 +62,7 @@ export class DefaultConfiguration extends Disposable { async initialize(): Promise { await this.initializeCachedConfigurationDefaultsOverrides(); this._configurationModel = undefined; - this._register(this.configurationRegistry.onDidUpdateConfiguration(({ defaultsOverrides }) => this.onDidUpdateConfiguration(defaultsOverrides))); + this._register(this.configurationRegistry.onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(properties, defaultsOverrides))); return this.configurationModel; } @@ -93,9 +93,9 @@ export class DefaultConfiguration extends Disposable { return this.initiaizeCachedConfigurationDefaultsOverridesPromise; } - private onDidUpdateConfiguration(defaultsOverrides?: boolean): void { + private onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { this._configurationModel = undefined; - this._onDidChangeConfiguration.fire(this.configurationModel); + this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties }); if (defaultsOverrides) { this.updateCachedConfigurationDefaultsOverrides(); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 9e8880d70576f..81cb9f779a170 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -16,7 +16,7 @@ import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceIdentifier, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; @@ -137,7 +137,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat }); })); - this._register(this.defaultConfiguration.onDidChangeConfiguration(configurationModel => this.onDefaultConfigurationChanged(configurationModel))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties, defaults }) => this.onDefaultConfigurationChanged(defaults, properties))); this.workspaceEditingQueue = new Queue(); } @@ -663,10 +663,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } - private onDefaultConfigurationChanged(configurationModel: ConfigurationModel): void { + private onDefaultConfigurationChanged(configurationModel: ConfigurationModel, properties?: string[]): void { if (this.workspace) { const previousData = this._configuration.toData(); - const change = this._configuration.compareAndUpdateDefaultConfiguration(configurationModel); + const change = this._configuration.compareAndUpdateDefaultConfiguration(configurationModel, properties); if (this.remoteUserConfiguration) { this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reparse()); this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reparse()); @@ -1092,6 +1092,24 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema); jsonRegistry.registerSchema(folderSettingsSchemaId, workspaceSettingsSchema); } + + jsonRegistry.registerSchema(configurationDefaultsSchemaId, { + type: 'object', + description: localize('configurationDefaults.description', 'Contribute defaults for configurations'), + properties: { + ...machineOverridableSettings.properties, + ...windowSettings.properties, + ...resourceSettings.properties + }, + patternProperties: { + [OVERRIDE_PROPERTY_PATTERN]: { + type: 'object', + default: {}, + $ref: resourceLanguageSettingsSchemaId, + } + }, + additionalProperties: false + }); } private checkAndFilterPropertiesRequiringTrust(properties: IStringDictionary): IStringDictionary { diff --git a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts index b8011291bc6c2..05f5d53e8b31a 100644 --- a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts @@ -116,7 +116,7 @@ suite('DefaultConfiguration', () => { } }); - const actual = await promise; + const { defaults: actual } = await promise; assert.deepStrictEqual(actual.getValue('test.configurationDefaultsOverride'), 'overrideValue'); }); From 66603ad3fe8cf23e8422bfe6c44792fda36dbe8f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 30 Nov 2021 15:13:52 +0100 Subject: [PATCH 0176/2210] Fixes localization mistake. --- .../editor/contrib/unicodeHighlighter/unicodeHighlighter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index ec9ea5542aa98..8f96230ff0e1e 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -522,11 +522,11 @@ interface IDisableUnicodeHighlightAction { export class DisableHighlightingOfAmbiguousCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction { public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters'; - public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters.shortLabel', ''); + public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters.shortLabel', 'Disable Ambiguous Highlight'); constructor() { super({ id: DisableHighlightingOfAmbiguousCharactersAction.ID, - label: nls.localize('action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable Ambiguous Highlight'), + label: nls.localize('action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable highlighting of ambiguous characters'), alias: 'Disable highlighting of ambiguous characters', precondition: undefined }); @@ -574,7 +574,7 @@ export class DisableHighlightingOfNonBasicAsciiCharactersAction extends EditorAc constructor() { super({ id: DisableHighlightingOfNonBasicAsciiCharactersAction.ID, - label: nls.localize('action.unicodeHighlight.dhowDisableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'), + label: nls.localize('action.unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'), alias: 'Disable highlighting of non basic ASCII characters', precondition: undefined }); From 0f599cb02f9a4ac9df2b7cc5ba8f8e97517187d3 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 30 Nov 2021 12:39:33 +0100 Subject: [PATCH 0177/2210] Fixes #138122: Option 'disableExtensions' is deprecated, please use 'disable-extensions' instead. --- src/vs/platform/environment/node/argv.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 135c53c78fd98..1c120d191ac3b 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -207,7 +207,9 @@ export function parseArgs(args: string[], options: OptionDescriptions, err if (o.deprecates && remainingArgs.hasOwnProperty(o.deprecates)) { if (!val) { val = remainingArgs[o.deprecates]; - errorReporter.onDeprecatedOption(o.deprecates, optionId); + if (val) { + errorReporter.onDeprecatedOption(o.deprecates, optionId); + } } delete remainingArgs[o.deprecates]; } From 257657fb23187b515f5273eb29b94525304d7ff7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 30 Nov 2021 15:21:37 +0100 Subject: [PATCH 0178/2210] fix https://github.com/microsoft/vscode/issues/137968 --- .../src/singlefolder-tests/workspace.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index bb9970bf9135b..d535935d5fa40 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -950,7 +950,7 @@ suite('vscode API - workspace', () => { we.insert(file1, new vscode.Position(0, 0), 'import1;'); const file2Name = basename(file2.fsPath); - const file2NewUri = vscode.Uri.parse(file2.toString().replace(file2Name, `new/${file2Name}`)); + const file2NewUri = vscode.Uri.joinPath(file2, `../new/${file2Name}`); we.renameFile(file2, file2NewUri); we.insert(file1, new vscode.Position(0, 0), 'import2;'); @@ -970,7 +970,7 @@ suite('vscode API - workspace', () => { we.insert(file1, new vscode.Position(0, 0), 'import2;'); const file2Name = basename(file2.fsPath); - const file2NewUri = vscode.Uri.parse(file2.toString().replace(file2Name, `new/${file2Name}`)); + const file2NewUri = vscode.Uri.joinPath(file2, `../new/${file2Name}`); we.renameFile(file2, file2NewUri); await vscode.workspace.applyEdit(we); @@ -1059,7 +1059,8 @@ suite('vscode API - workspace', () => { test('issue #107739 - Redo of rename Java Class name has no effect', async () => { const file = await createRandomFile('hello'); const fileName = basename(file.fsPath); - const newFile = vscode.Uri.parse(file.toString().replace(fileName, `${fileName}2`)); + + const newFile = vscode.Uri.joinPath(file, `../${fileName}2`); // apply edit { From 16b9fc761b46e8279b5a67c7770d1d1f93d3e504 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 30 Nov 2021 16:10:10 +0100 Subject: [PATCH 0179/2210] Fixes #138165 if trusted, don't include comment highlights, if untrusted, include comment highlights. --- .../editor/contrib/unicodeHighlighter/unicodeHighlighter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index 8f96230ff0e1e..ef9ea5e3b4979 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -191,14 +191,14 @@ function resolveOptions(trusted: boolean, options: InternalUnicodeHighlightOptio nonBasicASCII: false, ambiguousCharacters: true, invisibleCharacters: true, - includeComments: true, + includeComments: false, }; } else { defaults = { nonBasicASCII: true, ambiguousCharacters: true, invisibleCharacters: true, - includeComments: false, + includeComments: true, }; } From 585cf3780109465a344e4b8cfcc1d73b58babdac Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 30 Nov 2021 16:23:25 +0100 Subject: [PATCH 0180/2210] Fixes #138163 by removing unnecessary deriveFromWorkspaceTrust from some options. --- src/vs/editor/common/config/editorOptions.ts | 18 ++++++------- .../unicodeHighlighter/unicodeHighlighter.ts | 25 +++---------------- src/vs/monaco.d.ts | 4 +-- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 3ed60ecf2db50..f935e8fa7c31d 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -3261,8 +3261,8 @@ export const deriveFromWorkspaceTrust: DeriveFromWorkspaceTrust = 'deriveFromWor */ export interface IUnicodeHighlightOptions { nonBasicASCII?: boolean | DeriveFromWorkspaceTrust; - invisibleCharacters?: boolean | DeriveFromWorkspaceTrust; - ambiguousCharacters?: boolean | DeriveFromWorkspaceTrust; + invisibleCharacters?: boolean; + ambiguousCharacters?: boolean; includeComments?: boolean | DeriveFromWorkspaceTrust; /** * A list of allowed code points in a single string. @@ -3290,8 +3290,8 @@ class UnicodeHighlight extends BaseEditorOption(input.nonBasicASCII, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]), - invisibleCharacters: primitiveSet(input.invisibleCharacters, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]), - ambiguousCharacters: primitiveSet(input.ambiguousCharacters, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]), + invisibleCharacters: boolean(input.invisibleCharacters, this.defaultValue.invisibleCharacters), + ambiguousCharacters: boolean(input.ambiguousCharacters, this.defaultValue.ambiguousCharacters), includeComments: primitiveSet(input.includeComments, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]), allowedCharacters: string(input.allowedCharacters, ''), }; diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index ef9ea5e3b4979..799d846f36a31 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -185,28 +185,11 @@ type RemoveDeriveFromWorkspaceTrust = T extends DeriveFromWorkspaceTrust ? ne type ResolvedOptions = { [TKey in keyof InternalUnicodeHighlightOptions]: RemoveDeriveFromWorkspaceTrust }; function resolveOptions(trusted: boolean, options: InternalUnicodeHighlightOptions): ResolvedOptions { - let defaults; - if (trusted) { - defaults = { - nonBasicASCII: false, - ambiguousCharacters: true, - invisibleCharacters: true, - includeComments: false, - }; - } else { - defaults = { - nonBasicASCII: true, - ambiguousCharacters: true, - invisibleCharacters: true, - includeComments: true, - }; - } - return { - nonBasicASCII: options.nonBasicASCII !== deriveFromWorkspaceTrust ? options.nonBasicASCII : defaults.nonBasicASCII, - ambiguousCharacters: options.ambiguousCharacters !== deriveFromWorkspaceTrust ? options.ambiguousCharacters : defaults.ambiguousCharacters, - invisibleCharacters: options.invisibleCharacters !== deriveFromWorkspaceTrust ? options.invisibleCharacters : defaults.invisibleCharacters, - includeComments: options.includeComments !== deriveFromWorkspaceTrust ? options.includeComments : defaults.includeComments, + nonBasicASCII: options.nonBasicASCII !== deriveFromWorkspaceTrust ? options.nonBasicASCII : !trusted, + ambiguousCharacters: options.ambiguousCharacters, + invisibleCharacters: options.invisibleCharacters, + includeComments: options.includeComments !== deriveFromWorkspaceTrust ? options.includeComments : !trusted, allowedCharacters: options.allowedCharacters ?? [], }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index a84e7abca3e71..fce894c676eae 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3870,8 +3870,8 @@ declare namespace monaco.editor { */ export interface IUnicodeHighlightOptions { nonBasicASCII?: boolean | DeriveFromWorkspaceTrust; - invisibleCharacters?: boolean | DeriveFromWorkspaceTrust; - ambiguousCharacters?: boolean | DeriveFromWorkspaceTrust; + invisibleCharacters?: boolean; + ambiguousCharacters?: boolean; includeComments?: boolean | DeriveFromWorkspaceTrust; /** * A list of allowed code points in a single string. From d477eb1ee30fda8b17e2290e2b79207f1d366047 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 30 Nov 2021 17:27:04 +0100 Subject: [PATCH 0181/2210] Fixes #137512 by removing the href to #. Because the handler uses MOUSE_DOWN rather than CLICK, prevent default does not have the desired effect. The navigation causes a zoom reset. --- src/vs/base/browser/ui/hover/hoverWidget.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index dc4cb3054049b..3593cdcba61f2 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -55,7 +55,6 @@ export class HoverAction extends Disposable { this.actionContainer = dom.append(parent, $('div.action-container')); this.action = dom.append(this.actionContainer, $('a.action')); - this.action.setAttribute('href', '#'); this.action.setAttribute('role', 'button'); if (actionOptions.iconClass) { dom.append(this.action, $(`span.icon.${actionOptions.iconClass}`)); From c348f8164a03a2723756e54cae0ed5b4f6796583 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 30 Nov 2021 09:13:32 -0800 Subject: [PATCH 0182/2210] Support multi-select in process explorer's copy Fixes #137597 --- .../processExplorer/processExplorerMain.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 5f2040266cfc2..175a84483ab55 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -452,9 +452,23 @@ class ProcessExplorer { items.push({ label: localize('copy', "Copy"), click: () => { - const row = document.getElementById(`pid-${pid}`); - if (row) { - this.nativeHostService.writeClipboardText(row.innerText); + // Collect the selected pids + const selectionPids = this.tree?.getSelection()?.map(e => { + if (!e || !('pid' in e)) { + return undefined; + } + return e.pid; + }).filter(e => !!e) as number[]; + // If the selection does not contain the right clicked item, copy the right clicked + // item only. + if (!selectionPids?.includes(pid)) { + selectionPids.length = 0; + selectionPids.push(pid); + } + const rows = selectionPids?.map(e => document.getElementById(`pid-${e}`)).filter(e => !!e) as HTMLElement[]; + if (rows) { + const text = rows.map(e => e.innerText).filter(e => !!e) as string[]; + this.nativeHostService.writeClipboardText(text.join('\n')); } } }); From e6c9ee07961846f1b6876511f79d68fa6fbef166 Mon Sep 17 00:00:00 2001 From: Robert Jin Date: Sat, 6 Nov 2021 01:26:55 +0000 Subject: [PATCH 0183/2210] Update html-language-features to use doQuoteComplete --- .../src/{tagClosing.ts => autoInsertion.ts} | 39 ++++++++++++------- .../client/src/htmlClient.ts | 20 ++++++---- .../html-language-features/package.json | 6 +++ .../html-language-features/package.nls.json | 1 + .../server/src/htmlServer.ts | 22 ++++++++++- .../server/src/modes/htmlMode.ts | 12 ++++++ .../server/src/modes/languageModes.ts | 1 + 7 files changed, 79 insertions(+), 22 deletions(-) rename extensions/html-language-features/client/src/{tagClosing.ts => autoInsertion.ts} (59%) diff --git a/extensions/html-language-features/client/src/tagClosing.ts b/extensions/html-language-features/client/src/autoInsertion.ts similarity index 59% rename from extensions/html-language-features/client/src/tagClosing.ts rename to extensions/html-language-features/client/src/autoInsertion.ts index 0d0ef10f80419..9eb7a358e4285 100644 --- a/extensions/html-language-features/client/src/tagClosing.ts +++ b/extensions/html-language-features/client/src/autoInsertion.ts @@ -3,15 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, workspace, Disposable, TextDocument, Position, SnippetString, TextDocumentChangeEvent, TextDocumentChangeReason } from 'vscode'; +import { window, workspace, Disposable, TextDocument, Position, SnippetString, TextDocumentChangeEvent, TextDocumentChangeReason, Selection, TextDocumentContentChangeEvent } from 'vscode'; import { Runtime } from './htmlClient'; -export function activateTagClosing(tagProvider: (document: TextDocument, position: Position) => Thenable, supportedLanguages: { [id: string]: boolean }, configName: string, runtime: Runtime): Disposable { - +export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => Thenable, supportedLanguages: { [id: string]: boolean }, runtime: Runtime): Disposable { const disposables: Disposable[] = []; workspace.onDidChangeTextDocument(onDidChangeTextDocument, null, disposables); - let isEnabled = false; + let anyIsEnabled = false; + const isEnabled = { + 'autoQuote': false, + 'autoClose': false + }; updateEnabledState(); window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables); @@ -24,7 +27,7 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio }); function updateEnabledState() { - isEnabled = false; + anyIsEnabled = false; const editor = window.activeTextEditor; if (!editor) { return; @@ -33,14 +36,13 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio if (!supportedLanguages[document.languageId]) { return; } - if (!workspace.getConfiguration(undefined, document.uri).get(configName)) { - return; - } - isEnabled = true; + isEnabled['autoQuote'] = workspace.getConfiguration(undefined, document.uri).get('html.autoCreateQuotes') ?? false; + isEnabled['autoClose'] = workspace.getConfiguration(undefined, document.uri).get('html.autoClosingTags') ?? false; + anyIsEnabled = isEnabled['autoQuote'] || isEnabled['autoClose']; } function onDidChangeTextDocument({ document, contentChanges, reason }: TextDocumentChangeEvent) { - if (!isEnabled || contentChanges.length === 0 || reason === TextDocumentChangeReason.Undo || reason === TextDocumentChangeReason.Redo) { + if (!anyIsEnabled || contentChanges.length === 0 || reason === TextDocumentChangeReason.Undo || reason === TextDocumentChangeReason.Redo) { return; } const activeDocument = window.activeTextEditor && window.activeTextEditor.document; @@ -53,15 +55,20 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio const lastChange = contentChanges[contentChanges.length - 1]; const lastCharacter = lastChange.text[lastChange.text.length - 1]; - if (lastChange.rangeLength > 0 || lastCharacter !== '>' && lastCharacter !== '/') { - return; + if (isEnabled['autoQuote'] && lastChange.rangeLength === 0 && lastCharacter === '=') { + doAutoInsert('autoQuote', document, lastChange); + } else if (isEnabled['autoClose'] && lastChange.rangeLength === 0 && (lastCharacter === '>' || lastCharacter === '/')) { + doAutoInsert('autoClose', document, lastChange); } + } + + function doAutoInsert(kind: 'autoQuote' | 'autoClose', document: TextDocument, lastChange: TextDocumentContentChangeEvent) { const rangeStart = lastChange.range.start; const version = document.version; timeout = runtime.timer.setTimeout(() => { const position = new Position(rangeStart.line, rangeStart.character + lastChange.text.length); - tagProvider(document, position).then(text => { - if (text && isEnabled) { + provider(kind, document, position).then(text => { + if (text && isEnabled[kind]) { const activeEditor = window.activeTextEditor; if (activeEditor) { const activeDocument = activeEditor.document; @@ -69,6 +76,10 @@ export function activateTagClosing(tagProvider: (document: TextDocument, positio const selections = activeEditor.selections; if (selections.length && selections.some(s => s.active.isEqual(position))) { activeEditor.insertSnippet(new SnippetString(text), selections.map(s => s.active)); + if (kind === 'autoQuote') { + // Move all cursors one forward + activeEditor.selections = selections.map(s => new Selection(s.active.translate(0, 1), s.active.translate(0, 1))); + } } else { activeEditor.insertSnippet(new SnippetString(text), position); } diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 472977e652f6e..896382a583149 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -15,9 +15,9 @@ import { LanguageClientOptions, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, NotificationType, CommonLanguageClient } from 'vscode-languageclient'; -import { activateTagClosing } from './tagClosing'; import { FileSystemProvider, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; +import { activateAutoInsertion } from './autoInsertion'; namespace CustomDataChangedNotification { export const type: NotificationType = new NotificationType('html/customDataChanged'); @@ -27,9 +27,6 @@ namespace CustomDataContent { export const type: RequestType = new RequestType('html/customDataContent'); } -namespace TagCloseRequest { - export const type: RequestType = new RequestType('html/tag'); -} // experimental: semantic tokens interface SemanticTokenParams { textDocument: TextDocumentIdentifier; @@ -133,11 +130,20 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua client.onRequest(CustomDataContent.type, customDataSource.getContent); - let tagRequestor = (document: TextDocument, position: Position) => { + let insertRequestor = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => { let param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position); - return client.sendRequest(TagCloseRequest.type, param); + let request: RequestType; + switch (kind) { + case 'autoQuote': + request = new RequestType('html/quote'); + break; + case 'autoClose': + request = new RequestType('html/tag'); + break; + } + return client.sendRequest(request, param); }; - disposable = activateTagClosing(tagRequestor, { html: true, handlebars: true }, 'html.autoClosingTags', runtime); + let disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime); toDispose.push(disposable); disposable = client.onTelemetry(e => { diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 1a8722e02995b..b1bd5b5bafa45 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -197,6 +197,12 @@ "default": true, "description": "%html.validate.styles%" }, + "html.autoCreateQuotes": { + "type": "boolean", + "scope": "resource", + "default": true, + "description": "%html.autoCreateQuotes%" + }, "html.autoClosingTags": { "type": "boolean", "scope": "resource", diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index 00a218de43ed2..702ba05054148 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -27,6 +27,7 @@ "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", + "html.autoCreateQuotes": "Enable/disable auto creation of quotes for HTML attribute assignment.", "html.autoClosingTags": "Enable/disable autoclosing of HTML tags.", "html.completion.attributeDefaultValue": "Controls the default value for attributes when completion is accepted.", "html.completion.attributeDefaultValue.doublequotes": "Attribute value is set to \"\".", diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 8a053592ba031..150fdb77d1825 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -34,6 +34,10 @@ namespace CustomDataContent { export const type: RequestType = new RequestType('html/customDataContent'); } +namespace QuoteCreateRequest { + export const type: RequestType = new RequestType('html/quote'); +} + namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } @@ -83,7 +87,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let workspaceFoldersSupport = false; let foldingRangeLimit = Number.MAX_VALUE; - const customDataRequestService : CustomDataRequestService = { + const customDataRequestService: CustomDataRequestService = { getContent(uri: string) { return connection.sendRequest(CustomDataContent.type, uri); } @@ -483,6 +487,22 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }, [], `Error while computing color presentations for ${params.textDocument.uri}`, token); }); + connection.onRequest(QuoteCreateRequest.type, (params, token) => { + return runSafe(runtime, async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + const pos = params.position; + if (pos.character > 0) { + const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); + if (mode && mode.doAutoQuote) { + return mode.doAutoQuote(document, pos); + } + } + } + return null; + }, null, `Error while computing tag close actions for ${params.textDocument.uri}`, token); + }); + connection.onRequest(TagCloseRequest.type, (params, token) => { return runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts index 9c75531b3d07c..87d9ded6bc80e 100644 --- a/extensions/html-language-features/server/src/modes/htmlMode.ts +++ b/extensions/html-language-features/server/src/modes/htmlMode.ts @@ -55,6 +55,18 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: async getFoldingRanges(document: TextDocument): Promise { return htmlLanguageService.getFoldingRanges(document); }, + async doAutoQuote(document: TextDocument, position: Position, settings = workspace.settings) { + const htmlSettings = settings?.html; + const options = merge(htmlSettings?.suggest, {}); + options.attributeDefaultValue = htmlSettings?.completion?.attributeDefaultValue ?? 'doublequotes'; + + const offset = document.offsetAt(position); + const text = document.getText(); + if (offset > 0 && text.charAt(offset - 1) === '=') { + return htmlLanguageService.doQuoteComplete(document, position, htmlDocuments.get(document), options); + } + return null; + }, async doAutoClose(document: TextDocument, position: Position) { const offset = document.offsetAt(position); const text = document.getText(); diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts index 1e188e7c0b744..f0b3370a7d856 100644 --- a/extensions/html-language-features/server/src/modes/languageModes.ts +++ b/extensions/html-language-features/server/src/modes/languageModes.ts @@ -72,6 +72,7 @@ export interface LanguageMode { format?: (document: TextDocument, range: Range, options: FormattingOptions, settings?: Settings) => Promise; findDocumentColors?: (document: TextDocument) => Promise; getColorPresentations?: (document: TextDocument, color: Color, range: Range) => Promise; + doAutoQuote?: (document: TextDocument, position: Position) => Promise; doAutoClose?: (document: TextDocument, position: Position) => Promise; findMatchingTagPosition?: (document: TextDocument, position: Position) => Promise; getFoldingRanges?: (document: TextDocument) => Promise; From 4be98a3bce616ef941c1d5b6d544fa3826a7def5 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 30 Nov 2021 10:21:15 -0800 Subject: [PATCH 0184/2210] set default to pwsh instead of bash for #137725 --- test/automation/src/terminal.ts | 2 +- test/smoke/src/areas/terminal/terminal-profiles.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 5b9ff1c101258..4ed814cc3be6f 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -66,7 +66,7 @@ export class Terminal { } async runCommandWithValue(commandId: TerminalCommandIdWithValue, value?: string, altKey?: boolean): Promise { - const shouldKeepOpen = !!value || commandId === TerminalCommandIdWithValue.SelectDefaultProfile || commandId === TerminalCommandIdWithValue.NewWithProfile || commandId === TerminalCommandIdWithValue.Rename; + const shouldKeepOpen = !!value || commandId === TerminalCommandIdWithValue.NewWithProfile || commandId === TerminalCommandIdWithValue.Rename; await this.quickaccess.runCommand(commandId, shouldKeepOpen); if (value) { await this.code.waitForSetValue(QuickInput.QUICK_INPUT_INPUT, value); diff --git a/test/smoke/src/areas/terminal/terminal-profiles.test.ts b/test/smoke/src/areas/terminal/terminal-profiles.test.ts index 3132b3c835eea..8cd9def64b41a 100644 --- a/test/smoke/src/areas/terminal/terminal-profiles.test.ts +++ b/test/smoke/src/areas/terminal/terminal-profiles.test.ts @@ -37,7 +37,7 @@ export function setup(opts: ParsedArgs) { }); it('should set the default profile', async () => { - await terminal.runCommandWithValue(TerminalCommandIdWithValue.SelectDefaultProfile); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.SelectDefaultProfile, 'PowerShell'); await terminal.runCommand(TerminalCommandId.CreateNew); await terminal.assertSingleTab({ name: ANY_PROFILE_NAME }); }); From 4eb0002ed47fc4286b94f5f8efacfd71f408689e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 30 Nov 2021 10:30:18 -0800 Subject: [PATCH 0185/2210] Update diagram bg. --- .../docs/viewport-rendering.drawio.svg | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/docs/viewport-rendering.drawio.svg b/src/vs/workbench/contrib/notebook/browser/docs/viewport-rendering.drawio.svg index 5145d713e8e79..55b04f51b7641 100644 --- a/src/vs/workbench/contrib/notebook/browser/docs/viewport-rendering.drawio.svg +++ b/src/vs/workbench/contrib/notebook/browser/docs/viewport-rendering.drawio.svg @@ -1,4 +1,4 @@ - + @@ -384,28 +384,28 @@ - + -
    +
    - Cell Part updateLayout + Cell Part updatePartLayout
    - - Cell Part updateLayout + + Cell Part updatePartLayout - + -
    +
    Toolbar.renderCell @@ -413,16 +413,16 @@
    - + Toolbar.renderCell - + -
    +
    Toolbar.renderCell @@ -430,27 +430,27 @@
    - + Toolbar.renderCell - + -
    +
    - Toolbar.updateLayout + Toolbar.updatePartLayout
    - - Toolbar.updateLayo... + + Toolbar.updatePartLayout From 6d5fd7720bc802b88d3fb224c34576b2cf3a5076 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 30 Nov 2021 10:52:53 -0800 Subject: [PATCH 0186/2210] fixes #137214 --- src/vs/base/common/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index e4864becd37a8..86f0743bdc871 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -228,7 +228,7 @@ export class SubmenuAction implements IAction { readonly class: string | undefined; readonly tooltip: string = ''; readonly enabled: boolean = true; - readonly checked: boolean = false; + readonly checked: undefined = undefined; private readonly _actions: readonly IAction[]; get actions(): readonly IAction[] { return this._actions; } From 18725fc1474ac7c50006eaaafed7e7ff4a3bb98c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 30 Nov 2021 10:57:25 -0800 Subject: [PATCH 0187/2210] set default profile to pwsh for only windows for #137225 --- test/automation/src/terminal.ts | 2 +- test/smoke/src/areas/terminal/terminal-profiles.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 4ed814cc3be6f..247729162cc30 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -66,7 +66,7 @@ export class Terminal { } async runCommandWithValue(commandId: TerminalCommandIdWithValue, value?: string, altKey?: boolean): Promise { - const shouldKeepOpen = !!value || commandId === TerminalCommandIdWithValue.NewWithProfile || commandId === TerminalCommandIdWithValue.Rename; + const shouldKeepOpen = !!value || commandId === TerminalCommandIdWithValue.NewWithProfile || commandId === TerminalCommandIdWithValue.Rename || (commandId === TerminalCommandIdWithValue.SelectDefaultProfile && value !== 'PowerShell'); await this.quickaccess.runCommand(commandId, shouldKeepOpen); if (value) { await this.code.waitForSetValue(QuickInput.QUICK_INPUT_INPUT, value); diff --git a/test/smoke/src/areas/terminal/terminal-profiles.test.ts b/test/smoke/src/areas/terminal/terminal-profiles.test.ts index 8cd9def64b41a..19496712944e0 100644 --- a/test/smoke/src/areas/terminal/terminal-profiles.test.ts +++ b/test/smoke/src/areas/terminal/terminal-profiles.test.ts @@ -37,7 +37,7 @@ export function setup(opts: ParsedArgs) { }); it('should set the default profile', async () => { - await terminal.runCommandWithValue(TerminalCommandIdWithValue.SelectDefaultProfile, 'PowerShell'); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.SelectDefaultProfile, process.platform === 'win32' ? 'PowerShell' : undefined); await terminal.runCommand(TerminalCommandId.CreateNew); await terminal.assertSingleTab({ name: ANY_PROFILE_NAME }); }); From e89889fe54a9156498eded5378b4e614f7d93b94 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 30 Nov 2021 11:20:20 -0800 Subject: [PATCH 0188/2210] fix #137799 --- test/automation/src/terminal.ts | 2 +- test/smoke/src/areas/terminal/terminal-persistence.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 247729162cc30..bd0d35a91c87b 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -115,7 +115,7 @@ export class Terminal { const tabCount = (await this.code.waitForElements(Selector.Tabs, true)).length; const groups: TerminalGroup[] = []; for (let i = 0; i < tabCount; i++) { - const instance = await this.code.waitForElement(`${Selector.Tabs}[data-index="${i}"] ${Selector.TabsEntry}`); + const instance = await this.code.waitForElement(`${Selector.Tabs}[data-index="${i}"] ${Selector.TabsEntry}`, e => e?.textContent?.length ? e?.textContent?.length > 1 : false); const label: TerminalLabel = { name: instance.textContent.replace(/^[├┌└]\s*/, '') }; diff --git a/test/smoke/src/areas/terminal/terminal-persistence.test.ts b/test/smoke/src/areas/terminal/terminal-persistence.test.ts index 52b7d657483cf..79890d6ed8770 100644 --- a/test/smoke/src/areas/terminal/terminal-persistence.test.ts +++ b/test/smoke/src/areas/terminal/terminal-persistence.test.ts @@ -17,7 +17,7 @@ export function setup(opts: ParsedArgs) { describe('detach/attach', () => { // https://github.com/microsoft/vscode/issues/137799 - it.skip('should support basic reconnection', async () => { + it('should support basic reconnection', async () => { await terminal.runCommand(TerminalCommandId.CreateNew); // TODO: Handle passing in an actual regex, not string await terminal.assertTerminalGroups([ From 005947e3b8edf7a23bfdfbfcc44337873ef7d999 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 30 Nov 2021 11:54:28 -0800 Subject: [PATCH 0189/2210] fix #137722 fix #137808 --- test/automation/src/terminal.ts | 6 +++--- test/smoke/src/areas/terminal/terminal-editors.test.ts | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index bd0d35a91c87b..fe7cba5fdc4c7 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -15,7 +15,6 @@ export enum Selector { PlusButton = '.codicon-plus', EditorGroups = '.editor .split-view-view', EditorTab = '.terminal-tab', - EditorTabIcon = '.terminal-tab.codicon-', SingleTab = '.single-terminal-tab', Tabs = '.tabs-list .monaco-list-row', SplitButton = '.editor .codicon-split-horizontal' @@ -149,10 +148,11 @@ export class Terminal { await this.code.waitForElement(`${selector}`, singleTab => !!singleTab && !!singleTab?.textContent.match(nameRegex)); } if (color) { - await this.code.waitForElement(`${selector}.terminal-icon-terminal_ansi${color}`); + await this.code.waitForElement(`${selector}`, singleTab => !!singleTab && !!singleTab.className.includes(`terminal-icon-terminal_ansi${color}`)); } if (icon) { - await this.code.waitForElement(selector === Selector.EditorTab ? `${Selector.EditorTabIcon}${icon}` : `${selector} .codicon-${icon}`); + selector = selector === Selector.EditorTab ? selector : `${selector} .codicon`; + await this.code.waitForElement(`${selector}`, singleTab => !!singleTab && !!singleTab.className.includes(icon)); } } } diff --git a/test/smoke/src/areas/terminal/terminal-editors.test.ts b/test/smoke/src/areas/terminal/terminal-editors.test.ts index c69f87b756bf0..1dd76c6a03e15 100644 --- a/test/smoke/src/areas/terminal/terminal-editors.test.ts +++ b/test/smoke/src/areas/terminal/terminal-editors.test.ts @@ -16,16 +16,14 @@ export function setup(opts: ParsedArgs) { terminal = app.workbench.terminal; }); - // TODO: This was flaky in CI - it.skip('should update color of the tab', async () => { + it('should update color of the tab', async () => { await terminal.runCommand(TerminalCommandId.CreateNewEditor); const color = 'Cyan'; await terminal.runCommandWithValue(TerminalCommandIdWithValue.ChangeColor, color); await terminal.assertSingleTab({ color }, true); }); - // TODO: Flaky https://github.com/microsoft/vscode/issues/137808 - it.skip('should update icon of the tab', async () => { + it('should update icon of the tab', async () => { await terminal.runCommand(TerminalCommandId.CreateNewEditor); const icon = 'symbol-method'; await terminal.runCommandWithValue(TerminalCommandIdWithValue.ChangeIcon, icon); From faf6f0e1fdf856b5183a604537eb77d51d4605fe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 30 Nov 2021 12:19:10 -0800 Subject: [PATCH 0190/2210] Setup terminal tests for 3 retries to minimize impact on build This isn't a great long term solution but it should help reduce failures for now. Part of #137725 --- test/smoke/src/areas/terminal/terminal.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/smoke/src/areas/terminal/terminal.test.ts b/test/smoke/src/areas/terminal/terminal.test.ts index 7813375a72b1b..64c078a1e45a6 100644 --- a/test/smoke/src/areas/terminal/terminal.test.ts +++ b/test/smoke/src/areas/terminal/terminal.test.ts @@ -12,12 +12,15 @@ import { setup as setupTerminalProfileTests } from './terminal-profiles.test'; import { setup as setupTerminalTabsTests } from './terminal-tabs.test'; export function setup(opts: minimist.ParsedArgs) { - describe('Terminal', () => { + describe('Terminal', function () { // TODO: Enable terminal tests for non-web when the desktop driver is moved to playwright if (!opts.web) { return; } + // Retry tests 3 times to minimize build failures due to any flakiness + this.retries(3); + beforeSuite(opts); afterSuite(opts); From afd6cf4377506a69d20f66a50922cc50fb997f7f Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 30 Nov 2021 12:33:52 -0800 Subject: [PATCH 0191/2210] fix #138210 --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 9fe28adb47d1a..77f16013e6813 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1599,7 +1599,7 @@ export function registerTerminalActions() { const themeService = accessor.get(IThemeService); const groupService = accessor.get(ITerminalGroupService); const picks: ITerminalQuickPickItem[] = []; - if (!groupService.activeInstance || groupService.instances.length === 1) { + if (groupService.instances.length === 1) { return; } const otherInstances = groupService.instances.filter(i => i.instanceId !== groupService.activeInstance?.instanceId); @@ -1624,6 +1624,9 @@ export function registerTerminalActions() { }); } } + if (picks.length === 0) { + return; + } const result = await accessor.get(IQuickInputService).pick(picks, {}); if (result) { groupService.joinInstances([result.terminal, groupService.activeInstance!]); From 7f7fad2f9e74afabf134c61e2caf9cc80aa4f239 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 30 Nov 2021 12:38:52 -0800 Subject: [PATCH 0192/2210] fix #137798 --- test/smoke/src/areas/terminal/terminal-tabs.test.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/smoke/src/areas/terminal/terminal-tabs.test.ts b/test/smoke/src/areas/terminal/terminal-tabs.test.ts index b09485ea2cdd1..4372f1dd61587 100644 --- a/test/smoke/src/areas/terminal/terminal-tabs.test.ts +++ b/test/smoke/src/areas/terminal/terminal-tabs.test.ts @@ -8,7 +8,7 @@ import { ParsedArgs } from 'minimist'; import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation/out'; export function setup(opts: ParsedArgs) { - describe('Terminal Tabs', () => { + describe.only('Terminal Tabs', () => { // Acquire automation API let terminal: Terminal; before(function () { @@ -90,10 +90,17 @@ export function setup(opts: ParsedArgs) { it('should do nothing when join tabs is run with only one terminal', async () => { await terminal.runCommand(TerminalCommandId.Show); await terminal.runCommand(TerminalCommandId.Join); - await terminal.assertSingleTab({}); + await terminal.assertTerminalGroups([[{}]]); + }); + + it('should do nothing when join tabs is run with only split terminals', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.Split); + await terminal.runCommand(TerminalCommandId.Join); + await terminal.assertTerminalGroups([[{}], [{}]]); }); - it('should join tabs when more than one terminal', async () => { + it('should join tabs when more than one non-split terminal', async () => { await terminal.runCommand(TerminalCommandId.Show); await terminal.runCommand(TerminalCommandId.CreateNew); await terminal.runCommand(TerminalCommandId.Join); From 7b3078d9318848abe8b42bc7bdd3df1fa22ef94f Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 30 Nov 2021 12:48:58 -0800 Subject: [PATCH 0193/2210] fix #137795 --- test/automation/src/terminal.ts | 3 ++- test/smoke/src/areas/terminal/terminal-tabs.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index fe7cba5fdc4c7..5813f0215edb7 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -129,7 +129,8 @@ export class Terminal { } async getSingleTabName(): Promise { - return await (await this.code.waitForElement(Selector.SingleTab, singleTab => !!singleTab && singleTab?.textContent.length > 1)).textContent; + const tab = await this.code.waitForElement(Selector.SingleTab, singleTab => !!singleTab && singleTab?.textContent.length > 1); + return tab.textContent; } private async assertTabExpected(selector?: string, listIndex?: number, nameRegex?: RegExp, icon?: string, color?: string): Promise { diff --git a/test/smoke/src/areas/terminal/terminal-tabs.test.ts b/test/smoke/src/areas/terminal/terminal-tabs.test.ts index 4372f1dd61587..e4d409759b8e1 100644 --- a/test/smoke/src/areas/terminal/terminal-tabs.test.ts +++ b/test/smoke/src/areas/terminal/terminal-tabs.test.ts @@ -8,7 +8,7 @@ import { ParsedArgs } from 'minimist'; import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation/out'; export function setup(opts: ParsedArgs) { - describe.only('Terminal Tabs', () => { + describe('Terminal Tabs', () => { // Acquire automation API let terminal: Terminal; before(function () { @@ -60,12 +60,12 @@ export function setup(opts: ParsedArgs) { await terminal.assertSingleTab({ name }); }); - // Flaky: https://github.com/microsoft/vscode/issues/137795 - it.skip('should reset the tab name to the default value when no name is provided', async () => { + it('should reset the tab name to the default value when no name is provided', async () => { await terminal.runCommand(TerminalCommandId.Show); const defaultName = await terminal.getSingleTabName(); const name = 'my terminal name'; await terminal.runCommandWithValue(TerminalCommandIdWithValue.Rename, name); + await terminal.assertSingleTab({ name }); await terminal.runCommandWithValue(TerminalCommandIdWithValue.Rename, undefined); await terminal.assertSingleTab({ name: defaultName }); }); From f8756d07ef69cd157a878bf4da701593458ad3d9 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Tue, 30 Nov 2021 13:59:55 -0800 Subject: [PATCH 0194/2210] Add clarification to title schema Fixes #138194 --- src/vs/workbench/api/common/configurationExtensionPoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index dae0689cafe6c..ad92061a00ce3 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -23,7 +23,7 @@ const configurationEntrySchema: IJSONSchema = { defaultSnippets: [{ body: { title: '', properties: {} } }], properties: { title: { - description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'), + description: nls.localize('vscode.extension.contributes.configuration.title', 'A title for the current category of settings. This label will be rendered in the Settings editor as a subheading. If the title is the same as the extension display name, then the category will be grouped under the main extension heading.'), type: 'string' }, order: { From 8afc436bb625da324947d4bc580dbbe3e3148b2a Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 30 Nov 2021 15:53:46 -0800 Subject: [PATCH 0195/2210] fixes #138164 --- src/vs/workbench/browser/layout.ts | 2 +- src/vs/workbench/browser/workbench.contribution.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 91e523f7e666b..a249fb65d7862 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -51,7 +51,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { AuxiliaryBarPart, AUXILIARYBAR_ENABLED } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart'; -type PanelAlignment = 'left' | 'center' | 'right' | 'justified'; +type PanelAlignment = 'left' | 'center' | 'right' | 'justify'; export enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index f491019dfb7be..294c1574759ec 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -375,7 +375,7 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.experimental.panel.alignment': { 'type': 'string', - 'enum': ['left', 'center', 'right', 'justified'], + 'enum': ['left', 'center', 'right', 'justify'], 'default': 'center', 'description': localize('panelAlignment', "Controls the alignment of the panel (terminal, debug console, output, problems) and whether or not it spans beneath the side bar and side panel."), 'included': product.quality !== 'stable' From 6348b2bd3f753456afea214e06fdbc3367de2aea Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 30 Nov 2021 15:55:40 -0800 Subject: [PATCH 0196/2210] fixes #138172 --- src/vs/workbench/browser/workbench.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 294c1574759ec..0f5bf188fdc8c 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -377,7 +377,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['left', 'center', 'right', 'justify'], 'default': 'center', - 'description': localize('panelAlignment', "Controls the alignment of the panel (terminal, debug console, output, problems) and whether or not it spans beneath the side bar and side panel."), + 'description': localize('panelAlignment', "Controls the alignment of the panel (terminal, debug console, output, problems) and whether or not it spans beneath the side bar and side panel. Note that this setting only takes effect when the panel is positioned at the bottom of the screen."), 'included': product.quality !== 'stable' }, } From 18e0f6441164452776cfddc8f0bd2abaef467664 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 30 Nov 2021 16:01:19 -0800 Subject: [PATCH 0197/2210] fixes #138162 --- src/vs/workbench/browser/workbench.contribution.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 0f5bf188fdc8c..dcb346a93dd19 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -376,6 +376,12 @@ const registry = Registry.as(ConfigurationExtensions.Con 'workbench.experimental.panel.alignment': { 'type': 'string', 'enum': ['left', 'center', 'right', 'justify'], + 'enumDescriptions': [ + localize('panel.alignment.left', "The panel spans from the far left of the window to the right side of the editor area."), + localize('panel.alignment.center', "The panel spans beneath the editor area."), + localize('panel.alignment.right', "The panel spans from the left side of the editor area to the far right of the window."), + localize('panel.alignment.justify', "The panel spans the full width of the window."), + ], 'default': 'center', 'description': localize('panelAlignment', "Controls the alignment of the panel (terminal, debug console, output, problems) and whether or not it spans beneath the side bar and side panel. Note that this setting only takes effect when the panel is positioned at the bottom of the screen."), 'included': product.quality !== 'stable' From 1b731a039ed8eee7985507322981911207733c66 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 30 Nov 2021 16:07:34 -0800 Subject: [PATCH 0198/2210] fixes #138136 --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index dce54bc696634..c38cf061b95ba 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2123,7 +2123,7 @@ export class SCMViewPane extends ViewPane { sorter, keyboardNavigationLabelProvider, overrideStyles: { - listBackground: this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND + listBackground: this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BORDER }, accessibilityProvider: this.instantiationService.createInstance(SCMAccessibilityProvider) }) as WorkbenchCompressibleObjectTree; From 80b3c2f9ad37d8b112afb99e70445c0f48408d47 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 30 Nov 2021 16:53:51 -0800 Subject: [PATCH 0199/2210] fix build --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index c38cf061b95ba..959fa58ed3fc4 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2123,7 +2123,7 @@ export class SCMViewPane extends ViewPane { sorter, keyboardNavigationLabelProvider, overrideStyles: { - listBackground: this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BORDER + listBackground: this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND }, accessibilityProvider: this.instantiationService.createInstance(SCMAccessibilityProvider) }) as WorkbenchCompressibleObjectTree; From 0cc0904c565399781defa830facf43141db8b6f3 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 30 Nov 2021 16:55:00 -0800 Subject: [PATCH 0200/2210] fixes #138171 --- .../views/browser/viewDescriptorService.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 677caa66ec2e9..987e83b09fa60 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -19,6 +19,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { getViewsStateStorageId, ViewContainerModel } from 'vs/workbench/services/views/common/viewContainerModel'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; interface ICachedViewContainerInfo { containerId: string; @@ -94,6 +95,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @@ -117,6 +119,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // Register all containers that were registered before this ctor this.viewContainers.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); + // TODO@sbatten remove with setting for side panel/auxiliary bar + if (!this.configurationService.getValue('workbench.experimental.sidePanel.enabled')) { + this.fallbackDisabledAuxiliaryBar(); + } + this._register(this.viewsRegistry.onViewsRegistered(views => this.onDidRegisterViews(views))); this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(views, viewContainer))); @@ -184,6 +191,35 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } + private fallbackDisabledAuxiliaryBar(): void { + for (const [containerId, containerLocation] of this.cachedViewContainerInfo.entries()) { + if (containerLocation === ViewContainerLocation.AuxiliaryBar) { + const container = this.getViewContainerById(containerId); + if (!container || this.isGeneratedContainerId(containerId)) { + continue; + } + + this.moveViewContainerToLocation(container, this.getDefaultViewContainerLocation(container)); + } + } + + + for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { + const containerId = containerInfo.containerId; + + if (!this.isGeneratedContainerId(containerId) || this.cachedViewContainerInfo.get(containerId) !== ViewContainerLocation.AuxiliaryBar) { + continue; + } + + // check if view has been registered to default location + const viewContainer = this.viewsRegistry.getViewContainer(viewId); + const viewDescriptor = this.getViewDescriptorById(viewId); + if (viewContainer && viewDescriptor) { + this.addViews(viewContainer, [viewDescriptor]); + } + } + } + private fallbackOrphanedViews(): void { for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { const containerId = containerInfo.containerId; From d6f24e10d55b337bb09f5e8f39b8011fa4856d82 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 1 Dec 2021 09:20:41 +0100 Subject: [PATCH 0201/2210] Fix regression related to git.scanRepositories --- extensions/git/src/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 9cf79aadf8248..5e9fe6e9b8330 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -151,7 +151,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || []; for (const scanPath of scanPaths) { - if (scanPath !== '.git') { + if (scanPath === '.git') { continue; } From fe0b995d1be4fe8614794dc93dd345bed38a1b10 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 10:39:56 +0100 Subject: [PATCH 0202/2210] add pre-release version property to extension install telemetry --- .../extensionManagement/common/extensionManagementUtil.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index e82c61f474989..3c59a4d74f923 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -96,6 +96,7 @@ export function getLocalExtensionTelemetryData(extension: ILocalExtension): any "publisherId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "publisherName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "isPreReleaseVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "${include}": [ "${GalleryExtensionTelemetryData2}" @@ -110,6 +111,7 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension): publisherId: extension.publisherId, publisherName: extension.publisher, publisherDisplayName: extension.publisherDisplayName, + isPreReleaseVersion: extension.properties.isPreReleaseVersion, dependencies: !!(extension.properties.dependencies && extension.properties.dependencies.length > 0), ...extension.telemetryData }; From 3f7a6f0ebdb621e04ab122f6f5acdfbf56c92a24 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 11:02:51 +0100 Subject: [PATCH 0203/2210] Fix #138128 --- .../workbench/contrib/extensions/browser/extensionEditor.ts | 2 +- .../workbench/contrib/extensions/browser/extensionsList.ts | 2 +- .../contrib/extensions/browser/extensionsWidgets.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 82cd534445287..d05387b31377b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -429,7 +429,7 @@ export class ExtensionEditor extends EditorPane { } const widgets = [ - this.instantiationService.createInstance(PreReleaseIndicatorWidget, template.preRelease, { label: true, icon: false }), + this.instantiationService.createInstance(PreReleaseIndicatorWidget, template.preRelease, { label: true, icon: false, enableOnlyForInstalled: false }), remoteBadge, this.instantiationService.createInstance(InstallCountWidget, template.installCount, false), this.instantiationService.createInstance(RatingsWidget, template.rating, false) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 8233b9dff42d5..a9d0df174310b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -138,7 +138,7 @@ export class Renderer implements IPagedRenderer { extensionPackBadgeWidget, headerRemoteBadgeWidget, extensionHoverWidget, - this.instantiationService.createInstance(PreReleaseIndicatorWidget, preRelease, { icon: true, label: false }), + this.instantiationService.createInstance(PreReleaseIndicatorWidget, preRelease, { icon: true, label: false, enableOnlyForInstalled: true }), this.instantiationService.createInstance(SyncIgnoredWidget, syncIgnore), this.instantiationService.createInstance(ExtensionActivationStatusWidget, activationStatus, true), this.instantiationService.createInstance(InstallCountWidget, installCount, true), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index d96ecff7da9a9..538b2d417a206 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -162,7 +162,7 @@ export class PreReleaseIndicatorWidget extends ExtensionWidget { constructor( private readonly container: HTMLElement, - private readonly options: { label: boolean, icon: boolean }, + private readonly options: { label: boolean, icon: boolean, enableOnlyForInstalled: boolean }, ) { super(); container.classList.add('extension-pre-release'); @@ -180,6 +180,10 @@ export class PreReleaseIndicatorWidget extends ExtensionWidget { return; } + if (this.options.enableOnlyForInstalled && this.extension.state !== ExtensionState.Installed) { + return; + } + if (this.options?.icon) { append(this.container, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); } From 5ec7a99d3e3600a0762bf1d75248edb828927010 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 11:06:21 +0100 Subject: [PATCH 0204/2210] fix #138121 --- .../contrib/extensions/browser/media/extensionEditor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 0819697e27ed5..d4d41664b832c 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -98,6 +98,7 @@ -webkit-user-select: none; background-color: var(--vscode-extensionIcon-preReleaseForeground); color: #ffffff; + white-space: nowrap; } .monaco-workbench.vs .extension-editor > .header > .details > .title > .preview { From d60b844aa3192971757eb28dd8cef5a6a4ee063b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 1 Dec 2021 11:34:18 +0100 Subject: [PATCH 0205/2210] smoke - :up: retry timeout --- test/automation/src/code.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index a1c951d790b0e..95471907abff4 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -58,7 +58,7 @@ async function poll( fn: () => Thenable, acceptFn: (result: T) => boolean, timeoutMessage: string, - retryCount: number = 200, + retryCount: number = 400, retryInterval: number = 100 // millis ): Promise { let trial = 1; From a227323c7f1d47821bf6ade7bbb1358c307239b1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 1 Dec 2021 11:43:40 +0100 Subject: [PATCH 0206/2210] Fixes #138184 --- .../contrib/unicodeHighlighter/unicodeHighlighter.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index 799d846f36a31..51c7fca2aef5c 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -603,9 +603,16 @@ export class ShowExcludeOptions extends EditorAction { run(): Promise; } + function getExcludeCharFromBeingHighlightedLabel(codePoint: number) { + if (InvisibleCharacters.isInvisibleCharacter(codePoint)) { + return nls.localize('unicodeHighlight.excludeInvisibleCharFromBeingHighlighted', 'Exclude {0} (invisible) from being highlighted', `U+${codePoint.toString(16)}`); + } + return nls.localize('unicodeHighlight.excludeCharFromBeingHighlighted', 'Exclude {0} from being highlighted', `U+${codePoint.toString(16)} "${char}"`); + } + const options: ExtendedOptions[] = [ { - label: nls.localize('unicodeHighlight.excludeCharFromBeingHighlighted', 'Exclude {0} from being highlighted', `U+${codePoint.toString(16)} "${char}"`), + label: getExcludeCharFromBeingHighlightedLabel(codePoint), run: async () => { const existingValue = configurationService.getValue(unicodeHighlightConfigKeys.allowedCharacters); let value: string; From 2a9311659f1cfddc221116e77b5c5094ce40ec13 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 1 Dec 2021 11:44:51 +0100 Subject: [PATCH 0207/2210] invisibile -> invisible character --- src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index 51c7fca2aef5c..de8faaa18985a 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -605,7 +605,7 @@ export class ShowExcludeOptions extends EditorAction { function getExcludeCharFromBeingHighlightedLabel(codePoint: number) { if (InvisibleCharacters.isInvisibleCharacter(codePoint)) { - return nls.localize('unicodeHighlight.excludeInvisibleCharFromBeingHighlighted', 'Exclude {0} (invisible) from being highlighted', `U+${codePoint.toString(16)}`); + return nls.localize('unicodeHighlight.excludeInvisibleCharFromBeingHighlighted', 'Exclude {0} (invisible character) from being highlighted', `U+${codePoint.toString(16)}`); } return nls.localize('unicodeHighlight.excludeCharFromBeingHighlighted', 'Exclude {0} from being highlighted', `U+${codePoint.toString(16)} "${char}"`); } From 4da629d0c132659b59ac3b869cfae15b20867660 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 1 Dec 2021 11:57:17 +0100 Subject: [PATCH 0208/2210] deriveFromWorkspaceTrust -> trueIfUntrusted --- src/vs/editor/common/config/editorOptions.ts | 20 +++++++++---------- .../unicodeHighlighter/unicodeHighlighter.ts | 10 +++++----- src/vs/monaco.d.ts | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index f935e8fa7c31d..c515e874f2279 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -3249,21 +3249,21 @@ class EditorScrollbar extends BaseEditorOption { constructor() { const defaults: InternalUnicodeHighlightOptions = { - nonBasicASCII: deriveFromWorkspaceTrust, + nonBasicASCII: trueIfUntrusted, invisibleCharacters: true, ambiguousCharacters: true, - includeComments: deriveFromWorkspaceTrust, + includeComments: trueIfUntrusted, allowedCharacters: '', }; @@ -3302,7 +3302,7 @@ class UnicodeHighlight extends BaseEditorOption(input.nonBasicASCII, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]), + nonBasicASCII: primitiveSet(input.nonBasicASCII, trueIfUntrusted, [true, false, trueIfUntrusted]), invisibleCharacters: boolean(input.invisibleCharacters, this.defaultValue.invisibleCharacters), ambiguousCharacters: boolean(input.ambiguousCharacters, this.defaultValue.ambiguousCharacters), - includeComments: primitiveSet(input.includeComments, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]), + includeComments: primitiveSet(input.includeComments, trueIfUntrusted, [true, false, trueIfUntrusted]), allowedCharacters: string(input.allowedCharacters, ''), }; } diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index de8faaa18985a..ae5d978dede44 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -12,7 +12,7 @@ import { InvisibleCharacters } from 'vs/base/common/strings'; import 'vs/css!./unicodeHighlighter'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { DeriveFromWorkspaceTrust, deriveFromWorkspaceTrust, EditorOption, InternalUnicodeHighlightOptions, unicodeHighlightConfigKeys } from 'vs/editor/common/config/editorOptions'; +import { TrueIfUntrusted, trueIfUntrusted, EditorOption, InternalUnicodeHighlightOptions, unicodeHighlightConfigKeys } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecoration, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; @@ -181,15 +181,15 @@ export interface UnicodeHighlighterDecorationInfo { reason: UnicodeHighlighterReason; } -type RemoveDeriveFromWorkspaceTrust = T extends DeriveFromWorkspaceTrust ? never : T; -type ResolvedOptions = { [TKey in keyof InternalUnicodeHighlightOptions]: RemoveDeriveFromWorkspaceTrust }; +type RemoveTrueIfUntrusted = T extends TrueIfUntrusted ? never : T; +type ResolvedOptions = { [TKey in keyof InternalUnicodeHighlightOptions]: RemoveTrueIfUntrusted }; function resolveOptions(trusted: boolean, options: InternalUnicodeHighlightOptions): ResolvedOptions { return { - nonBasicASCII: options.nonBasicASCII !== deriveFromWorkspaceTrust ? options.nonBasicASCII : !trusted, + nonBasicASCII: options.nonBasicASCII === trueIfUntrusted ? !trusted : options.nonBasicASCII, ambiguousCharacters: options.ambiguousCharacters, invisibleCharacters: options.invisibleCharacters, - includeComments: options.includeComments !== deriveFromWorkspaceTrust ? options.includeComments : !trusted, + includeComments: options.includeComments === trueIfUntrusted ? !trusted : options.includeComments, allowedCharacters: options.allowedCharacters ?? [], }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index fce894c676eae..13dbf08eb8b8e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3863,16 +3863,16 @@ declare namespace monaco.editor { readonly scrollByPage: boolean; } - export type DeriveFromWorkspaceTrust = 'deriveFromWorkspaceTrust'; + export type TrueIfUntrusted = 'trueIfUntrusted'; /** * Configuration options for unicode highlighting. */ export interface IUnicodeHighlightOptions { - nonBasicASCII?: boolean | DeriveFromWorkspaceTrust; + nonBasicASCII?: boolean | TrueIfUntrusted; invisibleCharacters?: boolean; ambiguousCharacters?: boolean; - includeComments?: boolean | DeriveFromWorkspaceTrust; + includeComments?: boolean | TrueIfUntrusted; /** * A list of allowed code points in a single string. */ From 8bc6f0c0dcfbdd6329fbd5690a89b75c03ce55db Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 1 Dec 2021 12:12:06 +0100 Subject: [PATCH 0209/2210] trueIfUntrusted -> inUntrustedWorkspace --- src/vs/editor/common/config/editorOptions.ts | 20 +++++++++---------- .../unicodeHighlighter/unicodeHighlighter.ts | 8 ++++---- src/vs/monaco.d.ts | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index c515e874f2279..98617176a0c02 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -3249,21 +3249,21 @@ class EditorScrollbar extends BaseEditorOption { constructor() { const defaults: InternalUnicodeHighlightOptions = { - nonBasicASCII: trueIfUntrusted, + nonBasicASCII: inUntrustedWorkspace, invisibleCharacters: true, ambiguousCharacters: true, - includeComments: trueIfUntrusted, + includeComments: inUntrustedWorkspace, allowedCharacters: '', }; @@ -3302,7 +3302,7 @@ class UnicodeHighlight extends BaseEditorOption(input.nonBasicASCII, trueIfUntrusted, [true, false, trueIfUntrusted]), + nonBasicASCII: primitiveSet(input.nonBasicASCII, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), invisibleCharacters: boolean(input.invisibleCharacters, this.defaultValue.invisibleCharacters), ambiguousCharacters: boolean(input.ambiguousCharacters, this.defaultValue.ambiguousCharacters), - includeComments: primitiveSet(input.includeComments, trueIfUntrusted, [true, false, trueIfUntrusted]), + includeComments: primitiveSet(input.includeComments, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), allowedCharacters: string(input.allowedCharacters, ''), }; } diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index ae5d978dede44..296ba331806ab 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -12,7 +12,7 @@ import { InvisibleCharacters } from 'vs/base/common/strings'; import 'vs/css!./unicodeHighlighter'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { TrueIfUntrusted, trueIfUntrusted, EditorOption, InternalUnicodeHighlightOptions, unicodeHighlightConfigKeys } from 'vs/editor/common/config/editorOptions'; +import { InUntrustedWorkspace, inUntrustedWorkspace, EditorOption, InternalUnicodeHighlightOptions, unicodeHighlightConfigKeys } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecoration, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; @@ -181,15 +181,15 @@ export interface UnicodeHighlighterDecorationInfo { reason: UnicodeHighlighterReason; } -type RemoveTrueIfUntrusted = T extends TrueIfUntrusted ? never : T; +type RemoveTrueIfUntrusted = T extends InUntrustedWorkspace ? never : T; type ResolvedOptions = { [TKey in keyof InternalUnicodeHighlightOptions]: RemoveTrueIfUntrusted }; function resolveOptions(trusted: boolean, options: InternalUnicodeHighlightOptions): ResolvedOptions { return { - nonBasicASCII: options.nonBasicASCII === trueIfUntrusted ? !trusted : options.nonBasicASCII, + nonBasicASCII: options.nonBasicASCII === inUntrustedWorkspace ? !trusted : options.nonBasicASCII, ambiguousCharacters: options.ambiguousCharacters, invisibleCharacters: options.invisibleCharacters, - includeComments: options.includeComments === trueIfUntrusted ? !trusted : options.includeComments, + includeComments: options.includeComments === inUntrustedWorkspace ? !trusted : options.includeComments, allowedCharacters: options.allowedCharacters ?? [], }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 13dbf08eb8b8e..38f9d640e7e96 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3863,16 +3863,16 @@ declare namespace monaco.editor { readonly scrollByPage: boolean; } - export type TrueIfUntrusted = 'trueIfUntrusted'; + export type InUntrustedWorkspace = 'inUntrustedWorkspace'; /** * Configuration options for unicode highlighting. */ export interface IUnicodeHighlightOptions { - nonBasicASCII?: boolean | TrueIfUntrusted; + nonBasicASCII?: boolean | InUntrustedWorkspace; invisibleCharacters?: boolean; ambiguousCharacters?: boolean; - includeComments?: boolean | TrueIfUntrusted; + includeComments?: boolean | InUntrustedWorkspace; /** * A list of allowed code points in a single string. */ From 28820da7d873c640415838bd2965ff97247fbec3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 1 Dec 2021 12:16:57 +0100 Subject: [PATCH 0210/2210] Fixes #138059. Changes unicode highlight color from red to orange. --- src/vs/editor/common/view/editorColorRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index d12174bb94004..1f44cefb4587b 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -76,7 +76,7 @@ export const editorBracketPairGuideActiveBackground4 = registerColor('editorBrac export const editorBracketPairGuideActiveBackground5 = registerColor('editorBracketPairGuide.activeBackground5', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground5', 'Background color of active bracket pair guides (5). Requires enabling bracket pair guides.')); export const editorBracketPairGuideActiveBackground6 = registerColor('editorBracketPairGuide.activeBackground6', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground6', 'Background color of active bracket pair guides (6). Requires enabling bracket pair guides.')); -export const editorUnicodeHighlightBorder = registerColor('editorUnicodeHighlight.border', { dark: '#ff0000', light: '#ff0000', hc: '#ff0000' }, nls.localize('editorUnicodeHighlight.border', 'Border color used to highlight unicode characters.')); +export const editorUnicodeHighlightBorder = registerColor('editorUnicodeHighlight.border', { dark: '#BD9B03', light: '#CEA33D', hc: '#ff0000' }, nls.localize('editorUnicodeHighlight.border', 'Border color used to highlight unicode characters.')); // contains all color rules that used to defined in editor/browser/widget/editor.css From fad4e14fd719ace2cfde7a6cb3fbe4a628e68d78 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 1 Dec 2021 12:48:42 +0100 Subject: [PATCH 0211/2210] Add default snippet for automationProfile Fixes #138249 --- .../common/terminalPlatformConfiguration.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts index 9ea5b9acee8d7..41700242b2474 100644 --- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts +++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts @@ -121,6 +121,14 @@ const terminalPlatformConfiguration: IConfigurationNode = { 'anyOf': [ { type: 'null' }, terminalProfileSchema + ], + defaultSnippets: [ + { + body: { + path: '${1}', + icon: '${2}' + } + } ] }, [TerminalSettingId.AutomationProfileMacOs]: { @@ -131,6 +139,14 @@ const terminalPlatformConfiguration: IConfigurationNode = { 'anyOf': [ { type: 'null' }, terminalProfileSchema + ], + defaultSnippets: [ + { + body: { + path: '${1}', + icon: '${2}' + } + } ] }, [TerminalSettingId.AutomationProfileWindows]: { @@ -141,6 +157,14 @@ const terminalPlatformConfiguration: IConfigurationNode = { 'anyOf': [ { type: 'null' }, terminalProfileSchema + ], + defaultSnippets: [ + { + body: { + path: '${1}', + icon: '${2}' + } + } ] }, [TerminalSettingId.ShellLinux]: { From 9fee800d6877636f2e04981968621171aa8d9688 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 13:48:04 +0100 Subject: [PATCH 0212/2210] Fix #138231 --- .../common/extensionGalleryService.ts | 24 +++++++++++-------- .../common/extensionManagement.ts | 1 + .../browser/extensions.contribution.ts | 4 ++-- .../extensions/browser/extensionsActions.ts | 5 ++-- .../browser/extensionsWorkbenchService.ts | 4 ++++ .../contrib/extensions/common/extensions.ts | 1 + 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6f15f76499f68..d7bfcce88a7f5 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -384,7 +384,7 @@ export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], p return versions; } -function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], telemetryData?: any): IGalleryExtension { +function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], hasReleaseVersion: boolean, telemetryData?: IStringDictionary): IGalleryExtension { const latestVersion = galleryExtension.versions[0]; const assets = { manifest: getVersionAsset(version, AssetType.Manifest), @@ -428,6 +428,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller isPreReleaseVersion: isPreReleaseVersion(version) }, hasPreReleaseVersion: isPreReleaseVersion(latestVersion), + hasReleaseVersion, preview: getIsPreview(galleryExtension.flags), /* __GDPR__FRAGMENT__ "GalleryExtensionTelemetryData2" : { @@ -543,7 +544,8 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi }; } if (await this.isRawExtensionVersionCompatible(rawVersion, includePreRelease, allTargetPlatforms, targetPlatform)) { - return toExtension(rawExtension, rawVersion, allTargetPlatforms); + const hasReleaseVersion = rawExtension.versions.some(version => !isPreReleaseVersion(version)); + return toExtension(rawExtension, rawVersion, allTargetPlatforms, hasReleaseVersion); } } @@ -654,28 +656,29 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return { firstPage: extensions, total, pageSize: query.pageSize, getPage } as IPager; } - private async converToGalleryExtensions(rawGalleryExtensions: { rawGalleryExtension: IRawGalleryExtension, version?: string }[], includePreRelease: boolean, targetPlatform: TargetPlatform, telemetryData: (index: number) => any, token: CancellationToken): Promise { - const toExtensionWithLatestVersion = (galleryExtension: IRawGalleryExtension, index: number): IGalleryExtension => { + private async converToGalleryExtensions(rawGalleryExtensions: { rawGalleryExtension: IRawGalleryExtension, version?: string }[], includePreRelease: boolean, targetPlatform: TargetPlatform, telemetryData: (index: number) => IStringDictionary | undefined, token: CancellationToken): Promise { + const toExtensionWithLatestVersion = (galleryExtension: IRawGalleryExtension, index: number, hasReleaseVersion: boolean): IGalleryExtension => { const allTargetPlatforms = getAllTargetPlatforms(galleryExtension); let latestVersion = galleryExtension.versions[0]; latestVersion = galleryExtension.versions.find(version => version.version === latestVersion.version && isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(version), allTargetPlatforms, targetPlatform)) || latestVersion; - if (!includePreRelease && isPreReleaseVersion(latestVersion)) { + if (isPreReleaseVersion(latestVersion) && !includePreRelease) { latestVersion = galleryExtension.versions.find(version => version.version !== latestVersion.version && !isPreReleaseVersion(version)) || latestVersion; } - return toExtension(galleryExtension, latestVersion, allTargetPlatforms, telemetryData(index)); + return toExtension(galleryExtension, latestVersion, allTargetPlatforms, hasReleaseVersion, telemetryData(index)); }; const result: [number, IGalleryExtension][] = []; const preReleaseVersions = new Map(); for (let index = 0; index < rawGalleryExtensions.length; index++) { const { rawGalleryExtension, version } = rawGalleryExtensions[index]; + const hasReleaseVersion = rawGalleryExtension.versions.some(version => !isPreReleaseVersion(version)); if (version) { const versionAsset = rawGalleryExtension.versions.find(v => v.version === version); if (versionAsset) { - result.push([index, toExtension(rawGalleryExtension, versionAsset, getAllTargetPlatforms(rawGalleryExtension))]); + result.push([index, toExtension(rawGalleryExtension, versionAsset, getAllTargetPlatforms(rawGalleryExtension), hasReleaseVersion)]); } } else { - const extension = toExtensionWithLatestVersion(rawGalleryExtension, index); - if (extension.properties.isPreReleaseVersion && !includePreRelease) { + const extension = toExtensionWithLatestVersion(rawGalleryExtension, index, hasReleaseVersion); + if (extension.properties.isPreReleaseVersion) { preReleaseVersions.set(extension.identifier.uuid, index); } else { result.push([index, extension]); @@ -694,8 +697,9 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi throw new Error('Not all extensions with latest versions are returned'); } for (const rawGalleryExtension of galleryExtensions) { + const hasReleaseVersion = rawGalleryExtension.versions.some(version => !isPreReleaseVersion(version)); const index = preReleaseVersions.get(rawGalleryExtension.extensionId)!; - const extension = toExtensionWithLatestVersion(rawGalleryExtension, index); + const extension = toExtensionWithLatestVersion(rawGalleryExtension, index, hasReleaseVersion); result.push([index, extension]); } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index e3b8cb092d7d9..2f63c0971df81 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -257,6 +257,7 @@ export interface IGalleryExtension { lastUpdated: number; preview: boolean; hasPreReleaseVersion: boolean; + hasReleaseVersion: boolean; allTargetPlatforms: TargetPlatform[]; assets: IGalleryExtensionAssets; properties: IGalleryExtensionProperties; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index d5b4c93d3fb00..bcd803bef00c0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1187,7 +1187,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: '0_install', order: 1, - when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('extensionHasReleaseVersion'), ContextKeyExpr.has('showPreReleaseVersion'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, extensionId: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); @@ -1219,7 +1219,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: '0_install', order: 3, - when: ContextKeyExpr.and(ContextKeyExpr.has('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(ContextKeyExpr.has('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('extensionHasReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 884b922a9b228..9c33b9880402b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -297,7 +297,7 @@ export abstract class AbstractInstallAction extends ExtensionAction { this.enabled = false; if (this.extension && !this.extension.isBuiltin) { if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) { - this.enabled = !this.installPreReleaseVersion || this.extension.hasPreReleaseVersion; + this.enabled = this.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion; this.updateLabel(); } } @@ -909,6 +909,7 @@ function getContextMenuActionsGroups(extension: IExtension | undefined | null, c cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]); cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]); cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]); + cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]); } const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay)); @@ -1115,7 +1116,7 @@ export class SwitchToReleasedVersionAction extends ExtensionAction { } update(): void { - this.enabled = !!this.extension && this.extension.state === ExtensionState.Installed && !!this.extension.local?.isPreReleaseVersion; + this.enabled = !!this.extension && this.extension.state === ExtensionState.Installed && !!this.extension.local?.isPreReleaseVersion && !!this.extension.hasReleaseVersion; } override async run(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 443f77d562a79..2cd879d72aac3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -222,6 +222,10 @@ class Extension implements IExtension { return !!this.gallery?.hasPreReleaseVersion; } + get hasReleaseVersion(): boolean { + return !!this.gallery?.hasReleaseVersion; + } + private getLocal(preRelease: boolean): ILocalExtension | undefined { return this.local && !this.outdated && this.local.isPreReleaseVersion === preRelease ? this.local : undefined; } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 9b2a02eb47526..21ed3470d2bea 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -50,6 +50,7 @@ export interface IExtension { readonly version: string; readonly latestVersion: string; readonly hasPreReleaseVersion: boolean; + readonly hasReleaseVersion: boolean; readonly description: string; readonly url?: string; readonly repository?: string; From 1063c31c24e539dae3fff94460ee43fdff2ba8b9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 13:55:35 +0100 Subject: [PATCH 0213/2210] log provider id --- .../browser/userDataSyncWorkbenchService.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index d156f7dfea122..969029c4501ff 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -37,6 +37,7 @@ import { UserDataSyncStoreTypeSynchronizer } from 'vs/platform/userDataSync/comm type UserAccountClassification = { id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' }; + providerId: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' }; }; type FirstTimeSyncClassification = { @@ -45,6 +46,7 @@ type FirstTimeSyncClassification = { type UserAccountEvent = { id: string; + providerId: string; }; type FirstTimeSyncAction = 'pull' | 'push' | 'merge' | 'manual'; @@ -519,18 +521,20 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat if (!result) { return false; } - let sessionId: string, accountName: string, accountId: string; + let sessionId: string, accountName: string, accountId: string, authenticationProviderId: string; if (isAuthenticationProvider(result)) { const session = await this.authenticationService.createSession(result.id, result.scopes); sessionId = session.id; accountName = session.account.label; accountId = session.account.id; + authenticationProviderId = result.id; } else { sessionId = result.sessionId; accountName = result.accountName; accountId = result.accountId; + authenticationProviderId = result.authenticationProviderId; } - await this.switch(sessionId, accountName, accountId); + await this.switch(sessionId, accountName, accountId, authenticationProviderId); return true; } @@ -604,13 +608,13 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat return quickPickItems; } - private async switch(sessionId: string, accountName: string, accountId: string): Promise { + private async switch(sessionId: string, accountName: string, accountId: string, authenticationProviderId: string): Promise { const currentAccount = this.current; if (this.userDataAutoSyncEnablementService.isEnabled() && (currentAccount && currentAccount.accountName !== accountName)) { // accounts are switched while sync is enabled. } this.currentSessionId = sessionId; - this.telemetryService.publicLog2('sync.userAccount', { id: accountId }); + this.telemetryService.publicLog2('sync.userAccount', { id: accountId, providerId: authenticationProviderId }); await this.update(); } From 897d44194e54097ef23470fabc5725dc9f168aa3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 14:01:15 +0100 Subject: [PATCH 0214/2210] Update user agent header of MP requests --- .../extensionManagement/common/extensionGalleryService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index d7bfcce88a7f5..a4be6e4aa8aa2 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -1020,7 +1020,8 @@ export async function resolveMarketplaceHeaders(version: string, productService: } | undefined): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, - 'User-Agent': `VSCode ${version}` + 'X-Market-Client-Version': version, + 'User-Agent': `VSCode (${productService.nameShort})`, }; const uuid = await getServiceMachineId(environmentService, fileService, storageService); if (supportsTelemetry(productService, environmentService) && getTelemetryLevel(configurationService) === TelemetryLevel.USAGE) { From 94ab8f1288be9a21a9b6cbd0b3cdd7c186116874 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 14:11:15 +0100 Subject: [PATCH 0215/2210] fix tests --- .../extensions/test/electron-browser/extensionsActions.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index d67171bd069b6..7cb8d4de8e130 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -2343,6 +2343,7 @@ function aGalleryExtension(name: string, properties: any = {}, galleryExtensionP galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], targetPlatform, ...galleryExtensionProperties }; galleryExtension.assets = { ...galleryExtension.assets, ...assets }; galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() }; + galleryExtension.hasReleaseVersion = true; return galleryExtension; } From 06fad2502567607294313d19e3351272d5727aea Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 1 Dec 2021 15:01:45 +0100 Subject: [PATCH 0216/2210] Fixes #138185. Unicode highlight hover is no longer shown for unicode characters in comments. --- .../common/viewModel/viewModelDecorations.ts | 27 ++++++++------ .../unicodeHighlighter/unicodeHighlighter.ts | 35 ++++++++++++++++--- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index 76a3046fcc6dd..08c4f891c9253 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -122,16 +122,8 @@ export class ViewModelDecorations implements IDisposable { let modelDecoration = modelDecorations[i]; let decorationOptions = modelDecoration.options; - if (decorationOptions.hideInCommentTokens) { - let allTokensComments = testTokensInRange( - this.model, - modelDecoration.range, - (tokenType) => tokenType === StandardTokenType.Comment - ); - - if (allTokensComments) { - continue; - } + if (!isModelDecorationVisible(this.model, modelDecoration)) { + continue; } let viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration); @@ -176,6 +168,21 @@ export class ViewModelDecorations implements IDisposable { } } +export function isModelDecorationVisible(model: ITextModel, decoration: IModelDecoration): boolean { + if (decoration.options.hideInCommentTokens) { + const allTokensAreComments = testTokensInRange( + model, + decoration.range, + (tokenType) => tokenType === StandardTokenType.Comment + ); + if (allTokensAreComments) { + return false; + } + } + + return true; +} + /** * Calls the callback for every token that intersects the range. * If the callback returns `false`, iteration stops and `false` is returned. diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index 296ba331806ab..99a81fe5e2189 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -20,6 +20,7 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { UnicodeHighlighterOptions, UnicodeHighlighterReason, UnicodeHighlighterReasonKind, UnicodeTextModelHighlighter } from 'vs/editor/common/modes/unicodeTextModelHighlighter'; import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService'; import { IModeService } from 'vs/editor/common/services/modeService'; +import { isModelDecorationVisible } from 'vs/editor/common/viewModel/viewModelDecorations'; import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import { MarkdownHover, renderMarkdownHovers } from 'vs/editor/contrib/hover/markdownHoverParticipant'; import { BannerController } from 'vs/editor/contrib/unicodeHighlighter/bannerController'; @@ -255,8 +256,21 @@ class DocumentUnicodeHighlighter extends Disposable { if (!this._decorationIds.has(decorationId)) { return null; } - const range = this._editor.getModel().getDecorationRange(decorationId)!; - const text = this._editor.getModel().getValueInRange(range); + const model = this._editor.getModel(); + const range = model.getDecorationRange(decorationId)!; + if ( + !isModelDecorationVisible(model, { + range: range, + options: this._options.includeComments + ? DECORATION + : DECORATION_HIDE_IN_COMMENTS, + id: decorationId, + ownerId: 0, + }) + ) { + return null; + } + const text = model.getValueInRange(range); return { reason: computeReason(text, this._options)!, }; @@ -341,8 +355,21 @@ class ViewportUnicodeHighlighter extends Disposable { if (!this._decorationIds.has(decorationId)) { return null; } - const range = this._editor.getModel().getDecorationRange(decorationId)!; - const text = this._editor.getModel().getValueInRange(range); + const model = this._editor.getModel(); + const range = model.getDecorationRange(decorationId)!; + const text = model.getValueInRange(range); + if ( + !isModelDecorationVisible(model, { + range: range, + options: this._options.includeComments + ? DECORATION + : DECORATION_HIDE_IN_COMMENTS, + id: decorationId, + ownerId: 0, + }) + ) { + return null; + } return { reason: computeReason(text, this._options)!, }; From bdc02bd0970653d58469da73b39154d453d7fc73 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 15:12:20 +0100 Subject: [PATCH 0217/2210] Fix #138217 --- .../contrib/extensions/browser/extensionsViews.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index a533b16fce422..8aae9b8c1057f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -280,17 +280,19 @@ export class ExtensionsListView extends ViewPane { private async onContextMenu(e: IListContextMenuEvent): Promise { if (e.element) { const runningExtensions = await this.extensionService.getExtensions(); - const manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction); - manageExtensionAction.extension = e.element; + const disposables = new DisposableStore(); + const manageExtensionAction = disposables.add(this.instantiationService.createInstance(ManageExtensionAction)); + const extension = e.element ? this.extensionsWorkbenchService.local.find(local => areSameExtensions(local.identifier, e.element!.identifier) && (!e.element!.server || e.element!.server === local.server)) || e.element + : e.element; + manageExtensionAction.extension = extension; let groups: IAction[][] = []; if (manageExtensionAction.enabled) { groups = await manageExtensionAction.getActionGroups(runningExtensions); - - } else if (e.element) { - groups = getContextMenuActions(e.element, this.contextKeyService, this.instantiationService); + } else if (extension) { + groups = getContextMenuActions(extension, this.contextKeyService, this.instantiationService); groups.forEach(group => group.forEach(extensionAction => { if (extensionAction instanceof ExtensionAction) { - extensionAction.extension = e.element!; + extensionAction.extension = extension; } })); } @@ -303,6 +305,7 @@ export class ExtensionsListView extends ViewPane { getAnchor: () => e.anchor, getActions: () => actions, actionRunner: this.contextMenuActionRunner, + onHide: () => disposables.dispose() }); } } From 7549d51a10e3b8deb6969c5952268eefdbccdb50 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 15:47:18 +0100 Subject: [PATCH 0218/2210] Fix #138192 --- src/vs/workbench/api/common/configurationExtensionPoint.ts | 2 +- .../services/configuration/browser/configurationService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index ad92061a00ce3..b8a329ea4afe5 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -132,7 +132,7 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => { for (const key of Object.keys(overrides)) { if (!OVERRIDE_PROPERTY_REGEX.test(key)) { const registeredPropertyScheme = registeredProperties[key]; - if (registeredPropertyScheme.scope && !allowedScopes.includes(registeredPropertyScheme.scope)) { + if (registeredPropertyScheme?.scope && !allowedScopes.includes(registeredPropertyScheme.scope)) { extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for machine-overridable, window, resource and language overridable scoped settings are supported.", key)); delete overrides[key]; } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 81cb9f779a170..9c78c4abbc6ab 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -1155,7 +1155,7 @@ class UpdateExperimentalSettingsDefaults extends Disposable implements IWorkbenc const allProperties = this.configurationRegistry.getConfigurationProperties(); for (const property of properties) { const schema = allProperties[property]; - if (!schema.tags?.includes('experimental')) { + if (!schema?.tags?.includes('experimental')) { continue; } if (this.processedExperimentalSettings.has(property)) { From 5f7a6364a09061cb14e0095db68406b6e4d733a3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 1 Dec 2021 16:26:07 +0100 Subject: [PATCH 0219/2210] fix #136035 --- src/vs/workbench/browser/web.main.ts | 10 +-- .../lifecycle/browser/lifecycleService.ts | 61 +++++++------------ 2 files changed, 28 insertions(+), 43 deletions(-) diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index fdbd9256d6008..d47236a9f4299 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -96,7 +96,7 @@ class BrowserMain extends Disposable { const workbench = new Workbench(this.domElement, undefined, services.serviceCollection, services.logService); // Listeners - this.registerListeners(workbench, services.storageService, services.logService); + this.registerListeners(workbench); // Startup const instantiationService = workbench.startup(); @@ -135,14 +135,14 @@ class BrowserMain extends Disposable { }); } - private registerListeners(workbench: Workbench, storageService: BrowserStorageService, logService: ILogService): void { + private registerListeners(workbench: Workbench): void { // Workbench Lifecycle this._register(workbench.onWillShutdown(() => this.onWillShutdownDisposables.clear())); this._register(workbench.onDidShutdown(() => this.dispose())); } - private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IWorkbenchConfigurationService, logService: ILogService, storageService: BrowserStorageService }> { + private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IWorkbenchConfigurationService, logService: ILogService }> { const serviceCollection = new ServiceCollection(); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -245,7 +245,7 @@ class BrowserMain extends Disposable { mark('code/didInitRequiredUserData'); } - return { serviceCollection, configurationService, logService, storageService }; + return { serviceCollection, configurationService, logService }; } private async registerFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IFileService, remoteAgentService: IRemoteAgentService, logService: BufferLogService, logsPath: URI): Promise { @@ -337,7 +337,7 @@ class BrowserMain extends Disposable { }); } - private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService): Promise { + private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService): Promise { const storageService = new BrowserStorageService(payload, logService); try { diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index 4ede72ec90a00..b22cc8fa308be 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -19,7 +19,6 @@ export class BrowserLifecycleService extends AbstractLifecycleService { private ignoreBeforeUnload = false; - private didBeforeUnload = false; private didUnload = false; constructor( @@ -33,9 +32,6 @@ export class BrowserLifecycleService extends AbstractLifecycleService { private registerListeners(): void { - // Listen to `pageshow` to handle unsupported `persisted: true` cases - this._register(addDisposableListener(window, EventType.PAGE_SHOW, (e: PageTransitionEvent) => this.onLoad(e))); - // Listen to `beforeUnload` to support to veto this.beforeUnloadListener = addDisposableListener(window, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.onBeforeUnload(e)); @@ -47,37 +43,6 @@ export class BrowserLifecycleService extends AbstractLifecycleService { this.unloadListener = addDisposableListener(window, EventType.PAGE_HIDE, () => this.onUnload()); } - private onLoad(event: PageTransitionEvent): void { - - // We only really care about page-show events - // where the browser indicates to us that the - // page was restored from cache and not freshly - // loaded. - const wasRestoredFromCache = event.persisted; - if (!wasRestoredFromCache) { - return; - } - - // We only really care about `persisted` page-show - // events if there is a chance that we were unloaded - // before and now potentially have a disposed workbench - // that is non-functional. - // To be on the safe side, we ignore this event in any - // other cases to not accidentally reload the workbench. - const handleLoadEvent = this.didBeforeUnload; - if (!handleLoadEvent) { - return; - } - - // At this point, we know that the page was restored from - // cache even though it was potentially unloaded before, - // so in order to get back to a functional workbench, we - // currently can only reload the window - // Docs: https://web.dev/bfcache/#optimize-your-pages-for-bfcache - // Refs: https://github.com/microsoft/vscode/issues/136035 - this.withExpectedShutdown({ disableShutdownHandling: true }, () => window.location.reload()); - } - private onBeforeUnload(event: BeforeUnloadEvent): void { // Before unload ignored (once) @@ -141,8 +106,6 @@ export class BrowserLifecycleService extends AbstractLifecycleService { private doShutdown(vetoShutdown?: () => void): void { const logService = this.logService; - this.didBeforeUnload = true; - // Optimistically trigger a UI state flush // without waiting for it. The browser does // not guarantee that this is being executed @@ -188,9 +151,11 @@ export class BrowserLifecycleService extends AbstractLifecycleService { this.didUnload = true; - const logService = this.logService; + // Register a late `pageshow` listener specifically on unload + this._register(addDisposableListener(window, EventType.PAGE_SHOW, (e: PageTransitionEvent) => this.onLoadAfterUnload(e))); // First indicate will-shutdown + const logService = this.logService; this._onWillShutdown.fire({ join(promise, id) { logService.error(`[lifecycle] Long running operations during shutdown are unsupported in the web (id: ${id})`); @@ -201,6 +166,26 @@ export class BrowserLifecycleService extends AbstractLifecycleService { // Finally end with did-shutdown this._onDidShutdown.fire(); } + + private onLoadAfterUnload(event: PageTransitionEvent): void { + + // We only really care about page-show events + // where the browser indicates to us that the + // page was restored from cache and not freshly + // loaded. + const wasRestoredFromCache = event.persisted; + if (!wasRestoredFromCache) { + return; + } + + // At this point, we know that the page was restored from + // cache even though it was unloaded before, + // so in order to get back to a functional workbench, we + // currently can only reload the window + // Docs: https://web.dev/bfcache/#optimize-your-pages-for-bfcache + // Refs: https://github.com/microsoft/vscode/issues/136035 + this.withExpectedShutdown({ disableShutdownHandling: true }, () => window.location.reload()); + } } registerSingleton(ILifecycleService, BrowserLifecycleService); From 46190fd242581993924b0f76cce498254e71b5c8 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 12:23:33 +0100 Subject: [PATCH 0220/2210] reopen window on destroy needs to restore remoteAuthority --- src/vs/platform/windows/electron-main/window.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 6a0a44d144f7e..6c888d7d89c28 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -689,7 +689,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { }, urisToOpen: workspace ? [workspace] : undefined, forceEmpty, - forceNewWindow: true + forceNewWindow: true, + remoteAuthority: this.remoteAuthority }); window.focus(); } From 8906c59bd936fbbe57960471191715504a1526aa Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 12:24:05 +0100 Subject: [PATCH 0221/2210] use isEqualAuthority to compare remoteAuthority --- src/vs/platform/windows/electron-main/windowsMainService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 17b6c9540eb51..0601d8d342be2 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1184,7 +1184,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const extensionDevelopmentPathRemoteAuthority = getRemoteAuthority(url); if (extensionDevelopmentPathRemoteAuthority) { if (remoteAuthority) { - if (extensionDevelopmentPathRemoteAuthority !== remoteAuthority) { + if (!isEqualAuthority(extensionDevelopmentPathRemoteAuthority, remoteAuthority)) { this.logService.error('more than one extension development path authority'); } } else { From 5037285578382bedbb9d9317e41c98e579451644 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 16:04:20 +0100 Subject: [PATCH 0222/2210] store remoteAuthority for folders in backup service --- .../platform/backup/electron-main/backup.ts | 19 +- .../backup/electron-main/backupMainService.ts | 68 +++---- src/vs/platform/backup/node/backup.ts | 14 +- .../electron-main/backupMainService.test.ts | 166 ++++++++++-------- .../electron-main/windowsMainService.ts | 2 +- .../platform/workspaces/common/workspaces.ts | 25 ++- .../electron-main/workspacesMainService.ts | 4 +- .../workspacesManagementMainService.test.ts | 10 +- .../browser/actions/windowActions.ts | 16 +- .../workspaces/browser/workspacesService.ts | 4 +- .../test/browser/workbenchTestServices.ts | 4 +- 11 files changed, 189 insertions(+), 143 deletions(-) diff --git a/src/vs/platform/backup/electron-main/backup.ts b/src/vs/platform/backup/electron-main/backup.ts index b17d08ee46d04..1569939b36c9b 100644 --- a/src/vs/platform/backup/electron-main/backup.ts +++ b/src/vs/platform/backup/electron-main/backup.ts @@ -6,32 +6,21 @@ import { URI } from 'vs/base/common/uri'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IFolderBackupInfo, IWorkspaceBackupInfo, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; export const IBackupMainService = createDecorator('backupMainService'); -export interface IWorkspaceBackupInfo { - workspace: IWorkspaceIdentifier; - remoteAuthority?: string; -} - -export function isWorkspaceBackupInfo(obj: unknown): obj is IWorkspaceBackupInfo { - const candidate = obj as IWorkspaceBackupInfo; - - return candidate && isWorkspaceIdentifier(candidate.workspace); -} - export interface IBackupMainService { readonly _serviceBrand: undefined; isHotExitEnabled(): boolean; getWorkspaceBackups(): IWorkspaceBackupInfo[]; - getFolderBackupPaths(): URI[]; + getFolderBackupPaths(): IFolderBackupInfo[]; getEmptyWindowBackupPaths(): IEmptyWindowBackupInfo[]; registerWorkspaceBackupSync(workspace: IWorkspaceBackupInfo, migrateFrom?: string): string; - registerFolderBackupSync(folderUri: URI): string; + registerFolderBackupSync(folderUri: IFolderBackupInfo): string; registerEmptyWindowBackupSync(backupFolder?: string, remoteAuthority?: string): string; unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void; @@ -44,5 +33,5 @@ export interface IBackupMainService { * it checks for each backup location if any backups * are stored. */ - getDirtyWorkspaces(): Promise>; + getDirtyWorkspaces(): Promise>; } diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index 4a878b21c83b2..90155063a80b3 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -12,13 +12,13 @@ import { isLinux } from 'vs/base/common/platform'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Promises, RimRafMode, writeFileSync } from 'vs/base/node/pfs'; -import { IBackupMainService, isWorkspaceBackupInfo, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; -import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; +import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; +import { IBackupWorkspacesFormat, IDeprecatedBackupWorkspacesFormat, IEmptyWindowBackupInfo, isEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { HotExitConfiguration, IFilesConfiguration } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IFolderBackupInfo, isFolderBackupInfo, isWorkspaceIdentifier, IWorkspaceBackupInfo, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; export class BackupMainService implements IBackupMainService { @@ -28,7 +28,7 @@ export class BackupMainService implements IBackupMainService { protected workspacesJsonPath: string; private workspaces: IWorkspaceBackupInfo[] = []; - private folders: URI[] = []; + private folders: IFolderBackupInfo[] = []; private emptyWindows: IEmptyWindowBackupInfo[] = []; // Comparers for paths and resources that will @@ -47,7 +47,7 @@ export class BackupMainService implements IBackupMainService { } async initialize(): Promise { - let backups: IBackupWorkspacesFormat; + let backups: IBackupWorkspacesFormat & IDeprecatedBackupWorkspacesFormat; try { backups = JSON.parse(await Promises.readFile(this.workspacesJsonPath, 'utf8')); // invalid JSON or permission issue can happen here } catch (error) { @@ -74,10 +74,12 @@ export class BackupMainService implements IBackupMainService { this.workspaces = await this.validateWorkspaces(rootWorkspaces); // read folder backups - let workspaceFolders: URI[] = []; + let workspaceFolders: IFolderBackupInfo[] = []; try { - if (Array.isArray(backups.folderURIWorkspaces)) { - workspaceFolders = backups.folderURIWorkspaces.map(folder => URI.parse(folder)); + if (Array.isArray(backups.folderWorkspaceInfos)) { + workspaceFolders = backups.folderWorkspaceInfos.map(folder => ({ folderUri: URI.parse(folder.folderUri), remoteAuthority: folder.remoteAuthority })); + } else if (Array.isArray(backups.folderURIWorkspaces)) { + workspaceFolders = backups.folderURIWorkspaces.map(folder => ({ folderUri: URI.parse(folder), remoteAuthority: undefined })); } } catch (e) { // ignore URI parsing exceptions @@ -100,7 +102,7 @@ export class BackupMainService implements IBackupMainService { return this.workspaces.slice(0); // return a copy } - getFolderBackupPaths(): URI[] { + getFolderBackupPaths(): IFolderBackupInfo[] { if (this.isHotExitOnExitAndWindowClose()) { // Only non-folder windows are restored on main process launch when // hot exit is configured as onExitAndWindowClose. @@ -169,17 +171,17 @@ export class BackupMainService implements IBackupMainService { } } - registerFolderBackupSync(folderUri: URI): string { - if (!this.folders.some(folder => this.backupUriComparer.isEqual(folderUri, folder))) { - this.folders.push(folderUri); + registerFolderBackupSync(folderInfo: IFolderBackupInfo): string { + if (!this.folders.some(folder => this.backupUriComparer.isEqual(folderInfo.folderUri, folder.folderUri))) { + this.folders.push(folderInfo); this.saveSync(); } - return this.getBackupPath(this.getFolderHash(folderUri)); + return this.getBackupPath(this.getFolderHash(folderInfo)); } unregisterFolderBackupSync(folderUri: URI): void { - const index = this.folders.findIndex(folder => this.backupUriComparer.isEqual(folderUri, folder)); + const index = this.folders.findIndex(folder => this.backupUriComparer.isEqual(folderUri, folder.folderUri)); if (index !== -1) { this.folders.splice(index, 1); this.saveSync(); @@ -248,25 +250,26 @@ export class BackupMainService implements IBackupMainService { return result; } - private async validateFolders(folderWorkspaces: URI[]): Promise { + private async validateFolders(folderWorkspaces: IFolderBackupInfo[]): Promise { if (!Array.isArray(folderWorkspaces)) { return []; } - const result: URI[] = []; + const result: IFolderBackupInfo[] = []; const seenIds: Set = new Set(); - for (let folderURI of folderWorkspaces) { + for (let folderInfo of folderWorkspaces) { + const folderURI = folderInfo.folderUri; const key = this.backupUriComparer.getComparisonKey(folderURI); if (!seenIds.has(key)) { seenIds.add(key); - const backupPath = this.getBackupPath(this.getFolderHash(folderURI)); + const backupPath = this.getBackupPath(this.getFolderHash(folderInfo)); const hasBackups = await this.doHasBackups(backupPath); // If the folder has no backups, ignore it if (hasBackups) { if (folderURI.scheme !== Schemas.file || await Promises.exists(folderURI.fsPath)) { - result.push(folderURI); + result.push(folderInfo); } else { // If the folder has backups, but the target workspace is missing, convert backups to empty ones await this.convertToEmptyWindowBackup(backupPath); @@ -362,13 +365,13 @@ export class BackupMainService implements IBackupMainService { return true; } - async getDirtyWorkspaces(): Promise> { - const dirtyWorkspaces: Array = []; + async getDirtyWorkspaces(): Promise> { + const dirtyWorkspaces: Array = []; // Workspaces with backups for (const workspace of this.workspaces) { if ((await this.hasBackups(workspace))) { - dirtyWorkspaces.push(workspace.workspace); + dirtyWorkspaces.push(workspace); } } @@ -382,22 +385,22 @@ export class BackupMainService implements IBackupMainService { return dirtyWorkspaces; } - private hasBackups(backupLocation: IWorkspaceBackupInfo | IEmptyWindowBackupInfo | URI): Promise { + private hasBackups(backupLocation: IWorkspaceBackupInfo | IEmptyWindowBackupInfo | IFolderBackupInfo): Promise { let backupPath: string; + // Empty + if (isEmptyWindowBackupInfo(backupLocation)) { + backupPath = backupLocation.backupFolder; + } + // Folder - if (URI.isUri(backupLocation)) { + else if (isFolderBackupInfo(backupLocation)) { backupPath = this.getBackupPath(this.getFolderHash(backupLocation)); } // Workspace - else if (isWorkspaceBackupInfo(backupLocation)) { - backupPath = this.getBackupPath(backupLocation.workspace.id); - } - - // Empty else { - backupPath = backupLocation.backupFolder; + backupPath = this.getBackupPath(backupLocation.workspace.id); } return this.doHasBackups(backupPath); @@ -443,7 +446,7 @@ export class BackupMainService implements IBackupMainService { private serializeBackups(): IBackupWorkspacesFormat { return { rootURIWorkspaces: this.workspaces.map(workspace => ({ id: workspace.workspace.id, configURIPath: workspace.workspace.configPath.toString(), remoteAuthority: workspace.remoteAuthority })), - folderURIWorkspaces: this.folders.map(folder => folder.toString()), + folderWorkspaceInfos: this.folders.map(folder => ({ folderUri: folder.folderUri.toString(), remoteAuthority: folder.remoteAuthority })), emptyWorkspaceInfos: this.emptyWindows }; } @@ -452,7 +455,8 @@ export class BackupMainService implements IBackupMainService { return (Date.now() + Math.round(Math.random() * 1000)).toString(); } - protected getFolderHash(folderUri: URI): string { + protected getFolderHash(folder: IFolderBackupInfo): string { + const folderUri = folder.folderUri; let key: string; if (folderUri.scheme === Schemas.file) { diff --git a/src/vs/platform/backup/node/backup.ts b/src/vs/platform/backup/node/backup.ts index b054be3ee82d2..cf98a57aab288 100644 --- a/src/vs/platform/backup/node/backup.ts +++ b/src/vs/platform/backup/node/backup.ts @@ -5,13 +5,25 @@ export interface ISerializedWorkspace { id: string; configURIPath: string; remoteAuthority?: string; } +export interface ISerializedFolder { folderUri: string; remoteAuthority?: string; } + export interface IBackupWorkspacesFormat { rootURIWorkspaces: ISerializedWorkspace[]; - folderURIWorkspaces: string[]; + folderWorkspaceInfos: ISerializedFolder[]; emptyWorkspaceInfos: IEmptyWindowBackupInfo[]; } +/** Deprecated since 1.64 */ +export interface IDeprecatedBackupWorkspacesFormat { + folderURIWorkspaces: string[]; // replaced by folderWorkspaceInfos +} + export interface IEmptyWindowBackupInfo { backupFolder: string; remoteAuthority?: string; } + +export function isEmptyWindowBackupInfo(obj: unknown): obj is IEmptyWindowBackupInfo { + const candidate = obj as IEmptyWindowBackupInfo; + return typeof candidate?.backupFolder === 'string'; +} diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 9c9e8e4c712f7..384054607a646 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -14,7 +14,6 @@ import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; import { IBackupWorkspacesFormat, ISerializedWorkspace } from 'vs/platform/backup/node/backup'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -23,12 +22,14 @@ import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { ConsoleMainLogger, LogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; -import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IFolderBackupInfo, isFolderBackupInfo, IWorkspaceBackupInfo, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { Uri } from 'vscode'; flakySuite('BackupMainService', () => { - function assertEqualUris(actual: URI[], expected: URI[]) { - assert.deepStrictEqual(actual.map(a => a.toString()), expected.map(a => a.toString())); + function assertEqualFolderInfos(actual: IFolderBackupInfo[], expected: IFolderBackupInfo[]) { + const withUriAsString = (f: IFolderBackupInfo) => ({ folderUri: f.folderUri.toString(), remoteAuthority: f.remoteAuthority }); + assert.deepStrictEqual(actual.map(withUriAsString), expected.map(withUriAsString)); } function toWorkspace(path: string): IWorkspaceIdentifier { @@ -48,6 +49,10 @@ flakySuite('BackupMainService', () => { }; } + function toFolderBackupInfo(uri: Uri, remoteAuthority?: string): IFolderBackupInfo { + return { folderUri: uri, remoteAuthority }; + } + function toSerializedWorkspace(ws: IWorkspaceIdentifier): ISerializedWorkspace { return { id: ws.id, @@ -90,7 +95,7 @@ flakySuite('BackupMainService', () => { const fooFile = URI.file(platform.isWindows ? 'C:\\foo' : '/foo'); const barFile = URI.file(platform.isWindows ? 'C:\\bar' : '/bar'); - let service: BackupMainService & { toBackupPath(arg: URI | string): string, getFolderHash(folderUri: URI): string }; + let service: BackupMainService & { toBackupPath(arg: URI | string): string, getFolderHash(folder: IFolderBackupInfo): string }; let configService: TestConfigurationService; let environmentService: EnvironmentMainService; @@ -119,12 +124,12 @@ flakySuite('BackupMainService', () => { } toBackupPath(arg: URI | string): string { - const id = arg instanceof URI ? super.getFolderHash(arg) : arg; + const id = arg instanceof URI ? super.getFolderHash({ folderUri: arg }) : arg; return path.join(this.backupHome, id); } - override getFolderHash(folderUri: URI): string { - return super.getFolderHash(folderUri); + override getFolderHash(folder: IFolderBackupInfo): string { + return super.getFolderHash(folder); } }; @@ -138,18 +143,18 @@ flakySuite('BackupMainService', () => { test('service validates backup workspaces on startup and cleans up (folder workspaces)', async function () { // 1) backup workspace path does not exist - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); + service.registerFolderBackupSync(toFolderBackupInfo(barFile)); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); // 2) backup workspace path exists with empty contents within fs.mkdirSync(service.toBackupPath(fooFile)); fs.mkdirSync(service.toBackupPath(barFile)); - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); + service.registerFolderBackupSync(toFolderBackupInfo(barFile)); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); assert.ok(!fs.existsSync(service.toBackupPath(barFile))); @@ -158,10 +163,10 @@ flakySuite('BackupMainService', () => { fs.mkdirSync(service.toBackupPath(barFile)); fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); + service.registerFolderBackupSync(toFolderBackupInfo(barFile)); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); assert.ok(!fs.existsSync(service.toBackupPath(barFile))); @@ -171,7 +176,7 @@ flakySuite('BackupMainService', () => { fs.mkdirSync(service.toBackupPath(fooFile)); fs.mkdirSync(service.toBackupPath(barFile)); fs.mkdirSync(fileBackups); - service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); assert.strictEqual(service.getFolderBackupPaths().length, 1); assert.strictEqual(service.getEmptyWindowBackupPaths().length, 0); fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); @@ -229,7 +234,7 @@ flakySuite('BackupMainService', () => { const backupPathToMigrate = service.toBackupPath(fooFile); fs.mkdirSync(backupPathToMigrate); fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data'); - service.registerFolderBackupSync(URI.file(backupPathToMigrate)); + service.registerFolderBackupSync(toFolderBackupInfo(URI.file(backupPathToMigrate))); const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate); @@ -245,12 +250,12 @@ flakySuite('BackupMainService', () => { const backupPathToMigrate = service.toBackupPath(fooFile); fs.mkdirSync(backupPathToMigrate); fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data'); - service.registerFolderBackupSync(URI.file(backupPathToMigrate)); + service.registerFolderBackupSync(toFolderBackupInfo(URI.file(backupPathToMigrate))); const backupPathToPreserve = service.toBackupPath(barFile); fs.mkdirSync(backupPathToPreserve); fs.writeFileSync(path.join(backupPathToPreserve, 'backup.txt'), 'Some Data'); - service.registerFolderBackupSync(URI.file(backupPathToPreserve)); + service.registerFolderBackupSync(toFolderBackupInfo(URI.file(backupPathToPreserve))); const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate); @@ -265,54 +270,63 @@ flakySuite('BackupMainService', () => { suite('loadSync', () => { test('getFolderBackupPaths() should return [] when workspaces.json doesn\'t exist', () => { - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); }); test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON', async () => { fs.writeFileSync(backupWorkspacesPath, ''); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, '{]'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); fs.writeFileSync(backupWorkspacesPath, 'foo'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); }); - test('getFolderBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', async () => { + test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is absent', async () => { fs.writeFileSync(backupWorkspacesPath, '{}'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); }); - test('getFolderBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', async () => { - fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{}}'); + test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array', async () => { + fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{}}'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": ["bar"]}}'); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); + fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": ["bar"]}}'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": []}}'); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); + fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": []}}'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": "bar"}}'); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); + fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": "bar"}}'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":"foo"}'); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); + fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":"foo"}'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); - fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":1}'); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); + fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":1}'); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); + }); + + test('getFolderBackupPaths() should migrate folderURIWorkspaces', async () => { + await ensureFolderExists(existingTestFolder1); + + fs.writeFileSync(backupWorkspacesPath, JSON.stringify({ folderURIWorkspaces: [existingTestFolder1.toString()] })); + await service.initialize(); + assertEqualFolderInfos(service.getFolderBackupPaths(), [toFolderBackupInfo(existingTestFolder1)]); }); test('getFolderBackupPaths() should return [] when files.hotExit = "onExitAndWindowClose"', async () => { - service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); - assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]); + const fi = toFolderBackupInfo(URI.file(fooFile.fsPath.toUpperCase())); + service.registerFolderBackupSync(fi); + assertEqualFolderInfos(service.getFolderBackupPaths(), [fi]); configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE); await service.initialize(); - assertEqualUris(service.getFolderBackupPaths(), []); + assertEqualFolderInfos(service.getFolderBackupPaths(), []); }); test('getWorkspaceBackups() should return [] when workspaces.json doesn\'t exist', () => { @@ -383,7 +397,7 @@ flakySuite('BackupMainService', () => { const upperFooPath = fooFile.fsPath.toUpperCase(); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath)); assert.strictEqual(service.getWorkspaceBackups().length, 1); - assertEqualUris(service.getWorkspaceBackups().map(r => r.workspace.configPath), [URI.file(upperFooPath)]); + assert.deepStrictEqual(service.getWorkspaceBackups().map(r => r.workspace.configPath.toString()), [URI.file(upperFooPath).toString()]); configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); @@ -440,7 +454,7 @@ flakySuite('BackupMainService', () => { const workspacesJson: IBackupWorkspacesFormat = { rootURIWorkspaces: [], - folderURIWorkspaces: [existingTestFolder1.toString(), existingTestFolder1.toString()], + folderWorkspaceInfos: [{ folderUri: existingTestFolder1.toString() }, { folderUri: existingTestFolder1.toString() }], emptyWorkspaceInfos: [] }; await pfs.Promises.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); @@ -448,7 +462,7 @@ flakySuite('BackupMainService', () => { const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); + assert.deepStrictEqual(json.folderWorkspaceInfos, [{ folderUri: existingTestFolder1.toString() }]); }); test('should ignore duplicates on Windows and Mac (folder workspace)', async () => { @@ -457,14 +471,14 @@ flakySuite('BackupMainService', () => { const workspacesJson: IBackupWorkspacesFormat = { rootURIWorkspaces: [], - folderURIWorkspaces: [existingTestFolder1.toString(), existingTestFolder1.toString().toLowerCase()], + folderWorkspaceInfos: [{ folderUri: existingTestFolder1.toString() }, { folderUri: existingTestFolder1.toString().toLowerCase() }], emptyWorkspaceInfos: [] }; await pfs.Promises.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); await service.initialize(); const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); + assert.deepStrictEqual(json.folderWorkspaceInfos, [{ folderUri: existingTestFolder1.toString() }]); }); test('should ignore duplicates on Windows and Mac (root workspace)', async () => { @@ -478,7 +492,7 @@ flakySuite('BackupMainService', () => { const workspacesJson: IBackupWorkspacesFormat = { rootURIWorkspaces: [workspace1, workspace2, workspace3].map(toSerializedWorkspace), - folderURIWorkspaces: [], + folderWorkspaceInfos: [], emptyWorkspaceInfos: [] }; await pfs.Promises.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); @@ -497,12 +511,12 @@ flakySuite('BackupMainService', () => { suite('registerWindowForBackups', () => { test('should persist paths to workspaces.json (folder workspace)', async () => { - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); - assertEqualUris(service.getFolderBackupPaths(), [fooFile, barFile]); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); + service.registerFolderBackupSync(toFolderBackupInfo(barFile)); + assertEqualFolderInfos(service.getFolderBackupPaths(), [toFolderBackupInfo(fooFile), toFolderBackupInfo(barFile)]); const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepStrictEqual(json.folderURIWorkspaces, [fooFile.toString(), barFile.toString()]); + assert.deepStrictEqual(json.folderWorkspaceInfos, [{ folderUri: fooFile.toString() }, { folderUri: barFile.toString() }]); }); test('should persist paths to workspaces.json (root workspace)', async () => { @@ -511,7 +525,7 @@ flakySuite('BackupMainService', () => { const ws2 = toWorkspaceBackupInfo(barFile.fsPath); service.registerWorkspaceBackupSync(ws2); - assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [fooFile, barFile]); + assert.deepStrictEqual(service.getWorkspaceBackups().map(b => b.workspace.configPath.toString()), [fooFile.toString(), barFile.toString()]); assert.strictEqual(ws1.workspace.id, service.getWorkspaceBackups()[0].workspace.id); assert.strictEqual(ws2.workspace.id, service.getWorkspaceBackups()[1].workspace.id); @@ -525,18 +539,18 @@ flakySuite('BackupMainService', () => { }); test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', async () => { - service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); - assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]); + service.registerFolderBackupSync(toFolderBackupInfo(URI.file(fooFile.fsPath.toUpperCase()))); + assertEqualFolderInfos(service.getFolderBackupPaths(), [toFolderBackupInfo(URI.file(fooFile.fsPath.toUpperCase()))]); const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepStrictEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); + assert.deepStrictEqual(json.folderWorkspaceInfos, [{ folderUri: URI.file(fooFile.fsPath.toUpperCase()).toString() }]); }); test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', async () => { const upperFooPath = fooFile.fsPath.toUpperCase(); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath)); - assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [URI.file(upperFooPath)]); + assert.deepStrictEqual(service.getWorkspaceBackups().map(b => b.workspace.configPath.toString()), [URI.file(upperFooPath).toString()]); const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(buffer)); @@ -545,18 +559,18 @@ flakySuite('BackupMainService', () => { suite('removeBackupPathSync', () => { test('should remove folder workspaces from workspaces.json (folder workspace)', async () => { - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(barFile); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); + service.registerFolderBackupSync(toFolderBackupInfo(barFile)); service.unregisterFolderBackupSync(fooFile); const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(buffer)); - assert.deepStrictEqual(json.folderURIWorkspaces, [barFile.toString()]); + assert.deepStrictEqual(json.folderWorkspaceInfos, [{ folderUri: barFile.toString() }]); service.unregisterFolderBackupSync(barFile); const content = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json2 = (JSON.parse(content)); - assert.deepStrictEqual(json2.folderURIWorkspaces, []); + assert.deepStrictEqual(json2.folderWorkspaceInfos, []); }); test('should remove folder workspaces from workspaces.json (root workspace)', async () => { @@ -595,33 +609,37 @@ flakySuite('BackupMainService', () => { await ensureFolderExists(existingTestFolder1); // make sure backup folder exists, so the folder is not removed on loadSync - const workspacesJson: IBackupWorkspacesFormat = { rootURIWorkspaces: [], folderURIWorkspaces: [existingTestFolder1.toString()], emptyWorkspaceInfos: [] }; + const workspacesJson: IBackupWorkspacesFormat = { rootURIWorkspaces: [], folderWorkspaceInfos: [{ folderUri: existingTestFolder1.toString() }], emptyWorkspaceInfos: [] }; await pfs.Promises.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); await service.initialize(); service.unregisterFolderBackupSync(barFile); service.unregisterEmptyWindowBackupSync('test'); const content = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json = (JSON.parse(content)); - assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]); + assert.deepStrictEqual(json.folderWorkspaceInfos, [{ folderUri: existingTestFolder1.toString() }]); }); }); suite('getWorkspaceHash', () => { (platform.isLinux ? test.skip : test)('should ignore case on Windows and Mac', () => { + const assertFolderHash = (uri1: Uri, uri2: Uri) => { + assert.strictEqual(service.getFolderHash(toFolderBackupInfo(uri1)), service.getFolderHash(toFolderBackupInfo(uri2))); + }; + if (platform.isMacintosh) { - assert.strictEqual(service.getFolderHash(URI.file('/foo')), service.getFolderHash(URI.file('/FOO'))); + assertFolderHash(URI.file('/foo'), URI.file('/FOO')); } if (platform.isWindows) { - assert.strictEqual(service.getFolderHash(URI.file('c:\\foo')), service.getFolderHash(URI.file('C:\\FOO'))); + assertFolderHash(URI.file('c:\\foo'), URI.file('C:\\FOO')); } }); }); suite('mixed path casing', () => { test('should handle case insensitive paths properly (registerWindowForBackupsSync) (folder workspace)', () => { - service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); + service.registerFolderBackupSync(toFolderBackupInfo(URI.file(fooFile.fsPath.toUpperCase()))); if (platform.isLinux) { assert.strictEqual(service.getFolderBackupPaths().length, 2); @@ -644,12 +662,12 @@ flakySuite('BackupMainService', () => { test('should handle case insensitive paths properly (removeBackupPathSync) (folder workspace)', () => { // same case - service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); service.unregisterFolderBackupSync(fooFile); assert.strictEqual(service.getFolderBackupPaths().length, 0); // mixed case - service.registerFolderBackupSync(fooFile); + service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); service.unregisterFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); if (platform.isLinux) { @@ -662,7 +680,7 @@ flakySuite('BackupMainService', () => { suite('getDirtyWorkspaces', () => { test('should report if a workspace or folder has backups', async () => { - const folderBackupPath = service.registerFolderBackupSync(fooFile); + const folderBackupPath = service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); const backupWorkspaceInfo = toWorkspaceBackupInfo(fooFile.fsPath); const workspaceBackupPath = service.registerWorkspaceBackupSync(backupWorkspaceInfo); @@ -686,12 +704,12 @@ flakySuite('BackupMainService', () => { let found = 0; for (const dirtyWorkpspace of dirtyWorkspaces) { - if (URI.isUri(dirtyWorkpspace)) { - if (isEqual(fooFile, dirtyWorkpspace)) { + if (isFolderBackupInfo(dirtyWorkpspace)) { + if (isEqual(fooFile, dirtyWorkpspace.folderUri)) { found++; } } else { - if (isEqual(backupWorkspaceInfo.workspace.configPath, dirtyWorkpspace.configPath)) { + if (isEqual(backupWorkspaceInfo.workspace.configPath, dirtyWorkpspace.workspace.configPath)) { found++; } } diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 0601d8d342be2..1e2f0769ea7a0 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1398,7 +1398,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic if (isWorkspaceIdentifier(configuration.workspace)) { configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace: configuration.workspace, remoteAuthority: configuration.remoteAuthority }); } else if (isSingleFolderWorkspaceIdentifier(configuration.workspace)) { - configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.workspace.uri); + configuration.backupPath = this.backupMainService.registerFolderBackupSync({ folderUri: configuration.workspace.uri, remoteAuthority: configuration.remoteAuthority }); } else { const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder; configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(backupFolder, configuration.remoteAuthority); diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 9c7a5c2785feb..c1196f9b366fd 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -52,7 +52,7 @@ export interface IWorkspacesService { getRecentlyOpened(): Promise; // Dirty Workspaces - getDirtyWorkspaces(): Promise>; + getDirtyWorkspaces(): Promise>; } //#region Workspaces Recently Opened @@ -94,6 +94,29 @@ export function isRecentFile(curr: IRecent): curr is IRecentFile { return curr.hasOwnProperty('fileUri'); } +//#endregion + +//#region Backups + +export interface IWorkspaceBackupInfo { + workspace: IWorkspaceIdentifier; + remoteAuthority?: string; +} + +export interface IFolderBackupInfo { + folderUri: URI; + remoteAuthority?: string; +} + +export function isFolderBackupInfo(curr: IWorkspaceBackupInfo | IFolderBackupInfo): curr is IFolderBackupInfo { + return curr && curr.hasOwnProperty('folderUri'); +} + +export function isWorkspaceBackupInfo(curr: IWorkspaceBackupInfo | IFolderBackupInfo): curr is IWorkspaceBackupInfo { + return curr && curr.hasOwnProperty('workspace'); +} + + //#endregion //#region Identifiers / Payload diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index 3635fc0fe742d..e9b502b0148da 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -7,7 +7,7 @@ import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; -import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceBackupInfo, IFolderBackupInfo, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; @@ -73,7 +73,7 @@ export class WorkspacesMainService implements AddFirstParameterToFunctions> { + async getDirtyWorkspaces(): Promise> { return this.backupMainService.getDirtyWorkspaces(); } diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts index 09b4792890a80..28af06567b6b6 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts @@ -13,7 +13,7 @@ import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; +import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; @@ -22,7 +22,7 @@ import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv'; import { NullLogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IRawFileWorkspaceFolder, IRawUriWorkspaceFolder, IStoredWorkspace, IStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceIdentifier, rewriteWorkspaceFileForNewLocation, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { IFolderBackupInfo, IRawFileWorkspaceFolder, IRawUriWorkspaceFolder, IStoredWorkspace, IStoredWorkspaceFolder, IWorkspaceBackupInfo, IWorkspaceFolderCreationData, IWorkspaceIdentifier, rewriteWorkspaceFileForNewLocation, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { WorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; flakySuite('WorkspacesManagementMainService', () => { @@ -46,15 +46,15 @@ flakySuite('WorkspacesManagementMainService', () => { isHotExitEnabled(): boolean { throw new Error('Method not implemented.'); } getWorkspaceBackups(): IWorkspaceBackupInfo[] { throw new Error('Method not implemented.'); } - getFolderBackupPaths(): URI[] { throw new Error('Method not implemented.'); } + getFolderBackupPaths(): IFolderBackupInfo[] { throw new Error('Method not implemented.'); } getEmptyWindowBackupPaths(): IEmptyWindowBackupInfo[] { throw new Error('Method not implemented.'); } registerWorkspaceBackupSync(workspace: IWorkspaceBackupInfo, migrateFrom?: string | undefined): string { throw new Error('Method not implemented.'); } - registerFolderBackupSync(folderUri: URI): string { throw new Error('Method not implemented.'); } + registerFolderBackupSync(folder: IFolderBackupInfo): string { throw new Error('Method not implemented.'); } registerEmptyWindowBackupSync(backupFolder?: string | undefined, remoteAuthority?: string | undefined): string { throw new Error('Method not implemented.'); } unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void { throw new Error('Method not implemented.'); } unregisterFolderBackupSync(folderUri: URI): void { throw new Error('Method not implemented.'); } unregisterEmptyWindowBackupSync(backupFolder: string): void { throw new Error('Method not implemented.'); } - async getDirtyWorkspaces(): Promise<(IWorkspaceIdentifier | URI)[]> { return []; } + async getDirtyWorkspaces(): Promise<(IWorkspaceBackupInfo | IFolderBackupInfo)[]> { return []; } } function createUntitledWorkspace(folders: string[], names?: string[]) { diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 0e197d93f9155..e8d335ab2f440 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -18,7 +18,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IRecent, isRecentFolder, isRecentWorkspace, IWorkspacesService, IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IRecent, isRecentFolder, isRecentWorkspace, IWorkspacesService, IWorkspaceIdentifier, isFolderBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { FileKind } from 'vs/platform/files/common/files'; @@ -87,10 +87,10 @@ abstract class BaseOpenRecentAction extends Action2 { const dirtyFolders = new ResourceMap(); const dirtyWorkspaces = new ResourceMap(); for (const dirtyWorkspace of dirtyWorkspacesAndFolders) { - if (URI.isUri(dirtyWorkspace)) { - dirtyFolders.set(dirtyWorkspace, true); + if (isFolderBackupInfo(dirtyWorkspace)) { + dirtyFolders.set(dirtyWorkspace.folderUri, true); } else { - dirtyWorkspaces.set(dirtyWorkspace.configPath, dirtyWorkspace); + dirtyWorkspaces.set(dirtyWorkspace.workspace.configPath, dirtyWorkspace.workspace); hasWorkspaces = true; } } @@ -117,10 +117,10 @@ abstract class BaseOpenRecentAction extends Action2 { // Fill any backup workspace that is not yet shown at the end for (const dirtyWorkspaceOrFolder of dirtyWorkspacesAndFolders) { - if (URI.isUri(dirtyWorkspaceOrFolder) && !recentFolders.has(dirtyWorkspaceOrFolder)) { - workspacePicks.push(this.toQuickPick(modelService, modeService, labelService, { folderUri: dirtyWorkspaceOrFolder }, true)); - } else if (isWorkspaceIdentifier(dirtyWorkspaceOrFolder) && !recentWorkspaces.has(dirtyWorkspaceOrFolder.configPath)) { - workspacePicks.push(this.toQuickPick(modelService, modeService, labelService, { workspace: dirtyWorkspaceOrFolder }, true)); + if (isFolderBackupInfo(dirtyWorkspaceOrFolder) && !recentFolders.has(dirtyWorkspaceOrFolder.folderUri)) { + workspacePicks.push(this.toQuickPick(modelService, modeService, labelService, dirtyWorkspaceOrFolder, true)); + } else if (isWorkspaceBackupInfo(dirtyWorkspaceOrFolder) && !recentWorkspaces.has(dirtyWorkspaceOrFolder.workspace.configPath)) { + workspacePicks.push(this.toQuickPick(modelService, modeService, labelService, dirtyWorkspaceOrFolder, true)); } } diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index d73d392b0d71c..4b70bcb3701fd 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IEnterWorkspaceResult, IRecentlyOpened, restoreRecentlyOpened, IRecent, isRecentFile, isRecentFolder, toStoreData, IStoredWorkspaceFolder, getStoredWorkspaceFolder, WORKSPACE_EXTENSION, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IEnterWorkspaceResult, IRecentlyOpened, restoreRecentlyOpened, IRecent, isRecentFile, isRecentFolder, toStoreData, IStoredWorkspaceFolder, getStoredWorkspaceFolder, WORKSPACE_EXTENSION, IStoredWorkspace, IFolderBackupInfo, IWorkspaceBackupInfo } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { Emitter } from 'vs/base/common/event'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -167,7 +167,7 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS //#region Dirty Workspaces - async getDirtyWorkspaces(): Promise> { + async getDirtyWorkspaces(): Promise> { return []; // Currently not supported in web } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index eff636e571d4c..40e135ab1c801 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -119,7 +119,7 @@ import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textF import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; -import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IEnterWorkspaceResult, IFolderBackupInfo, IRecent, IRecentlyOpened, IWorkspaceBackupInfo, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalProfile, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal'; @@ -1718,7 +1718,7 @@ export class TestWorkspacesService implements IWorkspacesService { async removeRecentlyOpened(workspaces: URI[]): Promise { } async clearRecentlyOpened(): Promise { } async getRecentlyOpened(): Promise { return { files: [], workspaces: [] }; } - async getDirtyWorkspaces(): Promise<(URI | IWorkspaceIdentifier)[]> { return []; } + async getDirtyWorkspaces(): Promise<(IFolderBackupInfo | IWorkspaceBackupInfo)[]> { return []; } async enterWorkspace(path: URI): Promise { throw new Error('Method not implemented.'); } async getWorkspaceIdentifier(workspacePath: URI): Promise { throw new Error('Method not implemented.'); } } From 632853942496dabb5496557a6d9f873d9a5ff9c4 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 17:08:28 +0100 Subject: [PATCH 0223/2210] pass remote authority to addRecentlyOpened --- .../electron-main/workspacesHistoryMainService.ts | 14 ++++++++------ .../workspaces/browser/workspacesService.ts | 5 +++-- .../electron-sandbox/workspaceEditingService.ts | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index c5f4f948a43f1..2bfe5a5ee6d77 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -80,7 +80,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); // Add to history when entering workspace - this._register(this.workspacesManagementMainService.onDidEnterWorkspace(event => this.addRecentlyOpened([{ workspace: event.workspace }]))); + this._register(this.workspacesManagementMainService.onDidEnterWorkspace(event => this.addRecentlyOpened([{ workspace: event.workspace, remoteAuthority: event.window.remoteAuthority }]))); } private handleWindowsJumpList(): void { @@ -246,11 +246,13 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa const files: IRecentFile[] = []; // Add current workspace to beginning if set - const currentWorkspace = include?.config?.workspace; - if (isWorkspaceIdentifier(currentWorkspace) && !this.workspacesManagementMainService.isUntitledWorkspace(currentWorkspace)) { - workspaces.push({ workspace: currentWorkspace }); - } else if (isSingleFolderWorkspaceIdentifier(currentWorkspace)) { - workspaces.push({ folderUri: currentWorkspace.uri }); + if (include) { + const currentWorkspace = include.config?.workspace; + if (isWorkspaceIdentifier(currentWorkspace) && !this.workspacesManagementMainService.isUntitledWorkspace(currentWorkspace)) { + workspaces.push({ workspace: currentWorkspace, remoteAuthority: include.remoteAuthority }); + } else if (isSingleFolderWorkspaceIdentifier(currentWorkspace)) { + workspaces.push({ folderUri: currentWorkspace.uri, remoteAuthority: include.remoteAuthority }); + } } // Add currently files to open to the beginning if any diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index 4b70bcb3701fd..e4265208b5755 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -55,12 +55,13 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS private addWorkspaceToRecentlyOpened(): void { const workspace = this.workspaceService.getWorkspace(); + const remoteAuthority = this.environmentService.remoteAuthority; switch (this.workspaceService.getWorkbenchState()) { case WorkbenchState.FOLDER: - this.addRecentlyOpened([{ folderUri: workspace.folders[0].uri }]); + this.addRecentlyOpened([{ folderUri: workspace.folders[0].uri, remoteAuthority }]); break; case WorkbenchState.WORKSPACE: - this.addRecentlyOpened([{ workspace: { id: workspace.id, configPath: workspace.configuration! } }]); + this.addRecentlyOpened([{ workspace: { id: workspace.id, configPath: workspace.configuration! }, remoteAuthority }]); break; } } diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts index da5f69daf8d6d..4c38630f74394 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts @@ -125,7 +125,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi await this.workspacesService.addRecentlyOpened([{ label: this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }), workspace: newWorkspaceIdentifier, - remoteAuthority: this.environmentService.remoteAuthority + remoteAuthority: this.environmentService.remoteAuthority // remember whether this was a remote window }]); // Delete the untitled one From 8390ce8cc772eccf6ce668dd5114803dd0599cc7 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 17:11:12 +0100 Subject: [PATCH 0224/2210] use openWindow with the correct remoteAuthority: use null for recents to make sure a local window is opened --- src/vs/workbench/browser/actions/windowActions.ts | 15 ++++++++++++--- .../workbench/browser/actions/workspaceActions.ts | 2 +- .../browser/parts/titlebar/menubarControl.ts | 2 +- .../gettingStarted/browser/gettingStarted.ts | 5 ++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index e8d335ab2f440..eaba787ac2e50 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -39,6 +39,7 @@ export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; interface IRecentlyOpenedPick extends IQuickPickItem { resource: URI, openable: IWindowOpenable; + remoteAuthority: string | undefined; } const fileCategory = { value: localize('file', "File"), original: 'File' }; @@ -162,7 +163,10 @@ abstract class BaseOpenRecentAction extends Action2 { }); if (result.confirmed) { - hostService.openWindow([context.item.openable]); + hostService.openWindow( + [context.item.openable], { + remoteAuthority: context.item.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable + }); quickInputService.cancel(); } } @@ -170,7 +174,11 @@ abstract class BaseOpenRecentAction extends Action2 { }); if (pick) { - return hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd, forceReuseWindow: keyMods?.alt }); + return hostService.openWindow([pick.openable], { + forceNewWindow: keyMods?.ctrlCmd, + forceReuseWindow: keyMods?.alt, + remoteAuthority: pick.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable + }); } } @@ -215,7 +223,8 @@ abstract class BaseOpenRecentAction extends Action2 { description: parentPath, buttons: isDirty ? [isWorkspace ? this.dirtyRecentlyOpenedWorkspace : this.dirtyRecentlyOpenedFolder] : [this.removeFromRecentlyOpened], openable, - resource + resource, + remoteAuthority: recent.remoteAuthority }; } } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index d933c28912f2f..7c72ef45244dc 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -284,7 +284,7 @@ class DuplicateWorkspaceInNewWindowAction extends Action2 { const newWorkspace = await workspacesService.createUntitledWorkspace(folders, remoteAuthority); await workspaceEditingService.copyWorkspaceSettings(newWorkspace); - return hostService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); + return hostService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true, remoteAuthority }); } } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 07a549d5e1943..95ccdf43cb7c9 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -336,7 +336,7 @@ export abstract class MenubarControl extends Disposable { return this.hostService.openWindow([openable], { forceNewWindow: !!openInNewWindow, - remoteAuthority + remoteAuthority: remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable }); }); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index 3f708a5f9a755..9a81e3426fe96 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -1050,7 +1050,10 @@ export class GettingStartedPage extends EditorPane { link.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath)); link.addEventListener('click', e => { this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command: 'openRecent', argument: undefined }); - this.hostService.openWindow([windowOpenable], { forceNewWindow: e.ctrlKey || e.metaKey, remoteAuthority: recent.remoteAuthority }); + this.hostService.openWindow([windowOpenable], { + forceNewWindow: e.ctrlKey || e.metaKey, + remoteAuthority: recent.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable + }); e.preventDefault(); e.stopPropagation(); }); From 851e9e9619f12a950c984ad696502b9cb0cd0206 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Dec 2021 08:31:47 -0800 Subject: [PATCH 0225/2210] fix #138223 --- .../workbench/contrib/terminal/common/terminalConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ea28735145372..5802b5897ebe0 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -448,7 +448,7 @@ const terminalConfiguration: IConfigurationNode = { default: 30, }, [TerminalSettingId.LocalEchoEnabled]: { - description: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override `terminal.integrated.localEchoLatencyThreshold`"), + markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override `#terminal.integrated.localEchoLatencyThreshold#`"), type: 'string', enum: ['on', 'off', 'auto'], enumDescriptions: [ From c0f21fa378ccebcaf20ec48589d5de922abd9225 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Dec 2021 08:38:28 -0800 Subject: [PATCH 0226/2210] fix #138239, skip test --- test/smoke/src/areas/terminal/terminal-persistence.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/terminal/terminal-persistence.test.ts b/test/smoke/src/areas/terminal/terminal-persistence.test.ts index 79890d6ed8770..0994a6bb2d96f 100644 --- a/test/smoke/src/areas/terminal/terminal-persistence.test.ts +++ b/test/smoke/src/areas/terminal/terminal-persistence.test.ts @@ -41,7 +41,7 @@ export function setup(opts: ParsedArgs) { ]); }); - it('should persist buffer content', async () => { + it.skip('should persist buffer content', async () => { await terminal.runCommand(TerminalCommandId.CreateNew); // TODO: Handle passing in an actual regex, not string await terminal.assertTerminalGroups([ From 4a21019476dc1d414ec1e97e97b0c635b46bc308 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Dec 2021 08:50:26 -0800 Subject: [PATCH 0227/2210] fix #138238, skip failing test --- test/smoke/src/areas/terminal/terminal-profiles.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/terminal/terminal-profiles.test.ts b/test/smoke/src/areas/terminal/terminal-profiles.test.ts index 19496712944e0..6ea8e5f2af5b1 100644 --- a/test/smoke/src/areas/terminal/terminal-profiles.test.ts +++ b/test/smoke/src/areas/terminal/terminal-profiles.test.ts @@ -36,7 +36,7 @@ export function setup(opts: ParsedArgs) { await terminal.assertTerminalGroups([[{ name: CONTRIBUTED_PROFILE_NAME }, { name: CONTRIBUTED_PROFILE_NAME }]]); }); - it('should set the default profile', async () => { + it.skip('should set the default profile', async () => { await terminal.runCommandWithValue(TerminalCommandIdWithValue.SelectDefaultProfile, process.platform === 'win32' ? 'PowerShell' : undefined); await terminal.runCommand(TerminalCommandId.CreateNew); await terminal.assertSingleTab({ name: ANY_PROFILE_NAME }); From 38043d00949164b7eb5f14dbaa9b6680b5ac26be Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 17:11:12 +0100 Subject: [PATCH 0228/2210] use openWindow with the correct remoteAuthority: use null for recents to make sure a local window is opened --- src/vs/workbench/browser/actions/windowActions.ts | 15 ++++++++++++--- .../workbench/browser/actions/workspaceActions.ts | 2 +- .../browser/parts/titlebar/menubarControl.ts | 2 +- .../gettingStarted/browser/gettingStarted.ts | 5 ++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 0e197d93f9155..4fca69a919153 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -39,6 +39,7 @@ export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; interface IRecentlyOpenedPick extends IQuickPickItem { resource: URI, openable: IWindowOpenable; + remoteAuthority: string | undefined; } const fileCategory = { value: localize('file', "File"), original: 'File' }; @@ -162,7 +163,10 @@ abstract class BaseOpenRecentAction extends Action2 { }); if (result.confirmed) { - hostService.openWindow([context.item.openable]); + hostService.openWindow( + [context.item.openable], { + remoteAuthority: context.item.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable + }); quickInputService.cancel(); } } @@ -170,7 +174,11 @@ abstract class BaseOpenRecentAction extends Action2 { }); if (pick) { - return hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd, forceReuseWindow: keyMods?.alt }); + return hostService.openWindow([pick.openable], { + forceNewWindow: keyMods?.ctrlCmd, + forceReuseWindow: keyMods?.alt, + remoteAuthority: pick.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable + }); } } @@ -215,7 +223,8 @@ abstract class BaseOpenRecentAction extends Action2 { description: parentPath, buttons: isDirty ? [isWorkspace ? this.dirtyRecentlyOpenedWorkspace : this.dirtyRecentlyOpenedFolder] : [this.removeFromRecentlyOpened], openable, - resource + resource, + remoteAuthority: recent.remoteAuthority }; } } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index d933c28912f2f..7c72ef45244dc 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -284,7 +284,7 @@ class DuplicateWorkspaceInNewWindowAction extends Action2 { const newWorkspace = await workspacesService.createUntitledWorkspace(folders, remoteAuthority); await workspaceEditingService.copyWorkspaceSettings(newWorkspace); - return hostService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); + return hostService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true, remoteAuthority }); } } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 07a549d5e1943..95ccdf43cb7c9 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -336,7 +336,7 @@ export abstract class MenubarControl extends Disposable { return this.hostService.openWindow([openable], { forceNewWindow: !!openInNewWindow, - remoteAuthority + remoteAuthority: remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable }); }); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index 3f708a5f9a755..9a81e3426fe96 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -1050,7 +1050,10 @@ export class GettingStartedPage extends EditorPane { link.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath)); link.addEventListener('click', e => { this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command: 'openRecent', argument: undefined }); - this.hostService.openWindow([windowOpenable], { forceNewWindow: e.ctrlKey || e.metaKey, remoteAuthority: recent.remoteAuthority }); + this.hostService.openWindow([windowOpenable], { + forceNewWindow: e.ctrlKey || e.metaKey, + remoteAuthority: recent.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable + }); e.preventDefault(); e.stopPropagation(); }); From 29ea2755e84a9698ef806bec26ce9f83e254a0c5 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 1 Dec 2021 12:06:33 -0500 Subject: [PATCH 0229/2210] Fix #137974 --- extensions/git/src/commands.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 1b6f4aaea4355..b37d83ab4a46f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -686,6 +686,10 @@ export class CommandCenter { } const activeTextEditor = window.activeTextEditor; + // Must extract these now because opening a new document will change the activeTextEditor reference + const previousVisibleRange = activeTextEditor && activeTextEditor.visibleRanges[0]; + const previousURI = activeTextEditor && activeTextEditor.document.uri; + const previousSelection = activeTextEditor && activeTextEditor.selection; for (const uri of uris) { const opts: TextDocumentShowOptions = { @@ -702,18 +706,21 @@ export class CommandCenter { const document = window.activeTextEditor?.document; // If the document doesn't match what we opened then don't attempt to select the range - if (document?.uri.toString() !== uri.toString()) { + // Additioanlly if there was no previous document we don't have information to select a range + if (document?.uri.toString() !== uri.toString() || !activeTextEditor || !previousURI || !previousSelection) { continue; } // Check if active text editor has same path as other editor. we cannot compare via // URI.toString() here because the schemas can be different. Instead we just go by path. - if (activeTextEditor && activeTextEditor.document.uri.path === uri.path && document) { + if (previousURI.path === uri.path && document) { // preserve not only selection but also visible range - opts.selection = activeTextEditor.selection; - const previousVisibleRanges = activeTextEditor.visibleRanges; + opts.selection = previousSelection; const editor = await window.showTextDocument(document, opts); - editor.revealRange(previousVisibleRanges[0]); + // This should always be defined but just in case + if (previousVisibleRange) { + editor.revealRange(previousVisibleRange); + } } } } From b61667979948b67dd6749909b156cb3f78b93ce5 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 1 Dec 2021 09:23:53 -0800 Subject: [PATCH 0230/2210] use theme icon --- src/vs/workbench/browser/parts/titlebar/titlebarPart.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 48b0dfd791061..75bb1d6a0643b 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -19,7 +19,7 @@ import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { EditorResourceAccessor, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -47,6 +47,9 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +const layoutControlIcon = registerIcon('layout-control', Codicon.editorLayout, localize('layoutControlIcon', "Icon for the layout control menu found in the title bar.")); export class TitlebarPart extends Part implements ITitleService { @@ -418,7 +421,7 @@ export class TitlebarPart extends Part implements ITitleService { actionViewItemProvider: action => { if (action instanceof SubmenuAction) { return new DropdownMenuActionViewItem(action, action.actions, this.contextMenuService, { - classNames: Codicon.editorLayout.classNamesArray, + classNames: ThemeIcon.asClassNameArray(layoutControlIcon), anchorAlignmentProvider: () => AnchorAlignment.RIGHT, keybindingProvider: action => this.keybindingService.lookupKeybinding(action.id) }); From e126c61bdab348b191c4739f4e50531572baea23 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 1 Dec 2021 09:32:57 -0800 Subject: [PATCH 0231/2210] nb doc toc. --- .../notebook/browser/docs/notebook.layout.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md index 0d347c408e81d..7ca30321288a2 100644 --- a/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md +++ b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md @@ -1,6 +1,17 @@ The notebook editor is a virtualized list view rendered in two contexts (mainframe and webview/iframe). It's on top of the builtin list/tree view renderer but its experience is different from traditional list views like File Explorer and Settings Editor. This doc covers the architecture of the notebook editor and layout optimiziations we experimented. -# Archtecture +* [Architecture](#architecture) + * [Notebook model resolution](#notebook-model-resolution) + * [Viewport rendering](#viewport-rendering) + * [Cell rendering](#cell-rendering) + * [Focus tracking](#focus-tracking) +* [Optimizations](#optimizations) + * [Executing code cell followed by markdown cells](#executing-code-cell-followed-by-markdown-cells) + * [Re-executing code cell followed by markdown cells](#re-executing-code-cell-followed-by-markdown-cells) + * [Scrolling](#scrolling) + + +# Architecture ## Notebook model resolution From 387b6116a448eeea97613e5646153bcca9f27329 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 1 Dec 2021 18:38:13 +0100 Subject: [PATCH 0232/2210] After a reconnection, let the other party know (again) which messages have been received --- src/vs/base/parts/ipc/common/ipc.net.ts | 5 ++ .../base/parts/ipc/test/node/ipc.net.test.ts | 80 +++++++++++++++++-- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 4c8ddd29b4308..a9ab66240f9fa 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -907,6 +907,11 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketWriter.write(toSend[i]); } this._recvAckCheck(); + + // After a reconnection, let the other party know (again) which messages have been received. + // (perhaps the other party didn't receive a previous ACK) + this._incomingAckId = 0; + this._sendAckCheck(); } public acceptDisconnect(): void { diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index ec49f0069b6b1..6e913058854b0 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -90,7 +90,9 @@ class Ether { return this._b; } - constructor() { + constructor( + private readonly _wireLatency = 0 + ) { this._a = new EtherStream(this, 'a'); this._b = new EtherStream(this, 'b'); this._ab = []; @@ -98,13 +100,15 @@ class Ether { } public write(from: 'a' | 'b', data: Buffer): void { - if (from === 'a') { - this._ab.push(data); - } else { - this._ba.push(data); - } + setTimeout(() => { + if (from === 'a') { + this._ab.push(data); + } else { + this._ba.push(data); + } - setTimeout(() => this._deliver(), 0); + setTimeout(() => this._deliver(), 0); + }, this._wireLatency); } private _deliver(): void { @@ -365,6 +369,68 @@ suite('PersistentProtocol reconnection', () => { ); }); + test('acks are always sent after a reconnection', async () => { + await runWithFakedTimers( + { + useFakeTimers: true, + useSetImmediate: true, + maxTaskCount: 1000 + }, + async () => { + + const loadEstimator: ILoadEstimator = { + hasHighLoad: () => false + }; + const wireLatency = 1000; + const ether = new Ether(wireLatency); + const aSocket = new NodeSocket(ether.a); + const a = new PersistentProtocol(aSocket, null, loadEstimator); + const aMessages = new MessageStream(a); + const bSocket = new NodeSocket(ether.b); + const b = new PersistentProtocol(bSocket, null, loadEstimator); + const bMessages = new MessageStream(b); + + // send message a1 to have something unacknowledged + a.send(VSBuffer.fromString('a1')); + assert.strictEqual(a.unacknowledgedCount, 1); + assert.strictEqual(b.unacknowledgedCount, 0); + + // read message a1 at B + const a1 = await bMessages.waitForOne(); + assert.strictEqual(a1.toString(), 'a1'); + assert.strictEqual(a.unacknowledgedCount, 1); + assert.strictEqual(b.unacknowledgedCount, 0); + + // wait for B to send an ACK message, + // but resume before A receives it + await timeout(ProtocolConstants.AcknowledgeTime + wireLatency / 2); + assert.strictEqual(a.unacknowledgedCount, 1); + assert.strictEqual(b.unacknowledgedCount, 0); + + // simulate complete reconnection + aSocket.dispose(); + bSocket.dispose(); + const ether2 = new Ether(wireLatency); + const aSocket2 = new NodeSocket(ether2.a); + const bSocket2 = new NodeSocket(ether2.b); + b.beginAcceptReconnection(bSocket2, null); + b.endAcceptReconnection(); + a.beginAcceptReconnection(aSocket2, null); + a.endAcceptReconnection(); + + // wait for quite some time + await timeout(2 * ProtocolConstants.AcknowledgeTime + wireLatency); + assert.strictEqual(a.unacknowledgedCount, 0); + assert.strictEqual(b.unacknowledgedCount, 0); + + aMessages.dispose(); + bMessages.dispose(); + a.dispose(); + b.dispose(); + } + ); + }); + test('writing can be paused', async () => { await runWithFakedTimers({ useFakeTimers: true, maxTaskCount: 100 }, async () => { const loadEstimator: ILoadEstimator = { From 11d880ea13f70f245dcdce78baf70654da205dad Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 1 Dec 2021 19:31:55 +0100 Subject: [PATCH 0233/2210] Always send an ACK message after a reconnection --- src/vs/base/parts/ipc/common/ipc.net.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index a9ab66240f9fa..0caf27ec88b91 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -901,17 +901,18 @@ export class PersistentProtocol implements IMessagePassingProtocol { public endAcceptReconnection(): void { this._isReconnecting = false; + // After a reconnection, let the other party know (again) which messages have been received. + // (perhaps the other party didn't receive a previous ACK) + this._incomingAckId = this._incomingMsgId; + const msg = new ProtocolMessage(ProtocolMessageType.Ack, 0, this._incomingAckId, getEmptyBuffer()); + this._socketWriter.write(msg); + // Send again all unacknowledged messages const toSend = this._outgoingUnackMsg.toArray(); for (let i = 0, len = toSend.length; i < len; i++) { this._socketWriter.write(toSend[i]); } this._recvAckCheck(); - - // After a reconnection, let the other party know (again) which messages have been received. - // (perhaps the other party didn't receive a previous ACK) - this._incomingAckId = 0; - this._sendAckCheck(); } public acceptDisconnect(): void { From cd34b164975116879c776dd9162933d66e963760 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 1 Dec 2021 10:31:48 -0800 Subject: [PATCH 0234/2210] fixes #138153 --- src/vs/platform/telemetry/common/telemetryService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index c66501394a0a1..4fecc99a30a1c 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -205,7 +205,7 @@ export class TelemetryService implements ITelemetryService { // Regex which matches @*.site const emailRegex = /@[a-zA-Z0-9-.]+/; - const secretRegex = /\S*(key|token|sig|password|passwd|pwd)[="':\s]+\S*/; + const secretRegex = /(key|token|sig|password|passwd|pwd)[="':\s]/; // Check for common user data in the telemetry events if (secretRegex.test(value)) { From 5ad2e51f8c6f4969edce062948929c906b1cbec0 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 1 Dec 2021 13:49:50 -0500 Subject: [PATCH 0235/2210] Add signature to telemetry regex --- src/vs/platform/telemetry/common/telemetryService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 4fecc99a30a1c..9e72305381193 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -205,7 +205,7 @@ export class TelemetryService implements ITelemetryService { // Regex which matches @*.site const emailRegex = /@[a-zA-Z0-9-.]+/; - const secretRegex = /(key|token|sig|password|passwd|pwd)[="':\s]/; + const secretRegex = /(key|token|sig|signature|password|passwd|pwd)[="':\s]/; // Check for common user data in the telemetry events if (secretRegex.test(value)) { From b34ea772678742ecc2be9449e93820c929abe286 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 1 Dec 2021 10:56:06 -0800 Subject: [PATCH 0236/2210] fix #137333. --- .../workbench/contrib/notebook/browser/media/notebook.css | 6 +++--- .../contrib/notebook/browser/notebookEditorWidget.ts | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 0cca53087ecc5..0267e31a0b46d 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -882,19 +882,19 @@ flex-direction: row-reverse; } -.monaco-workbench .notebookOverlay .output .error::-webkit-scrollbar, +.monaco-workbench .notebookOverlay .output .error .traceback::-webkit-scrollbar, .monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar { width: 10px; /* width of the entire scrollbar */ height: 10px; } -.monaco-workbench .notebookOverlay .output .error::-webkit-scrollbar-thumb, +.monaco-workbench .notebookOverlay .output .error .traceback::-webkit-scrollbar-thumb, .monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar-thumb { height: 10px; width: 10px; } -.monaco-workbench .notebookOverlay .output .error, +.monaco-workbench .notebookOverlay .output .error .traceback, .monaco-workbench .notebookOverlay .output .output-plaintext { overflow-x: auto; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 05bc82e77054b..38080f694654f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2939,14 +2939,13 @@ registerThemingParticipant((theme, collector) => { const scrollbarSliderBackgroundColor = theme.getColor(listScrollbarSliderBackground); if (scrollbarSliderBackgroundColor) { collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider { background: ${scrollbarSliderBackgroundColor}; } `); - // collector.addRule(` .monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar-track { background: ${scrollbarSliderBackgroundColor}; } `); } const scrollbarSliderHoverBackgroundColor = theme.getColor(listScrollbarSliderHoverBackground); if (scrollbarSliderHoverBackgroundColor) { collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider:hover { background: ${scrollbarSliderHoverBackgroundColor}; } `); collector.addRule(` .monaco-workbench .notebookOverlay .output-plaintext::-webkit-scrollbar-thumb { background: ${scrollbarSliderHoverBackgroundColor}; } `); - collector.addRule(` .monaco-workbench .notebookOverlay .output .error::-webkit-scrollbar-thumb { background: ${scrollbarSliderHoverBackgroundColor}; } `); + collector.addRule(` .monaco-workbench .notebookOverlay .output .error .traceback::-webkit-scrollbar-thumb { background: ${scrollbarSliderHoverBackgroundColor}; } `); } const scrollbarSliderActiveBackgroundColor = theme.getColor(listScrollbarSliderActiveBackground); From 1db7410fbc36a5ee6b58ef59dd9f26af61b12d71 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 19:58:07 +0100 Subject: [PATCH 0237/2210] Default product icon preview doesn't work. Fixes #138201 --- .../contrib/themes/browser/themes.contribution.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 89cf39b1173d6..d4a7d66c1ee66 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -18,7 +18,7 @@ import { ColorScheme } from 'vs/platform/theme/common/theme'; import { colorThemeSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; import { IQuickInputButton, IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData'; +import { DEFAULT_PRODUCT_ICON_THEME_ID, ProductIconThemeData } from 'vs/workbench/services/themes/browser/productIconThemeData'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { ThrottledDelayer } from 'vs/base/common/async'; @@ -33,6 +33,7 @@ import { IExtensionResourceLoaderService } from 'vs/workbench/services/extension import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData'; export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.')); @@ -409,7 +410,7 @@ registerAction2(class extends Action2 { const picks: QuickPickInput[] = [ { type: 'separator', label: localize('fileIconThemeCategory', 'file icon themes') }, - { id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable File Icons') }, + { id: '', theme: FileIconThemeData.noIconTheme, label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable File Icons') }, ...toEntries(await themeService.getFileIconThemes()), ]; @@ -445,7 +446,7 @@ registerAction2(class extends Action2 { const picks: QuickPickInput[] = [ { type: 'separator', label: localize('productIconThemeCategory', 'product icon themes') }, - { id: DEFAULT_PRODUCT_ICON_THEME_ID, label: localize('defaultProductIconThemeLabel', 'Default') }, + { id: DEFAULT_PRODUCT_ICON_THEME_ID, theme: ProductIconThemeData.defaultTheme, label: localize('defaultProductIconThemeLabel', 'Default') }, ...toEntries(await themeService.getProductIconThemes()), ]; From 37569d14c65707ee55e31bc10b68425ea6bcf85e Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 1 Dec 2021 11:26:39 -0800 Subject: [PATCH 0238/2210] Update Codicons: Add `layout` icon https://github.com/microsoft/vscode-codicons/commit/a068597836854a1f306752050dcf8c2e39c38354 --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 68248 -> 68352 bytes src/vs/base/common/codicons.ts | 1 + .../browser/parts/titlebar/titlebarPart.ts | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index bd4c54780a0b1ab3b1e5aeb393c05be4cd8a4ca2..ae1034e794625fffff56592f250d23a9291012b6 100644 GIT binary patch delta 3738 zcmXZe4Oo@s83*wH|A2xYpn{4Z5{8yD6A(CjD4(K$BHsck3WqOILCH|j%7c!|F|Q4M z%eA)5)M}MinRL}=X3lDvIdRQdxmH%@+Ih^r%&axr{dlMT&i{P8zURH(`+n~8JaE5% z*AD;Q7;hNBtpMs8+C1%pGDdz21pEMuKEAYdee?8~PoshHr-8RRTADnK)xX(QF2|k| z?iP8&waE31JU=8Rw6t|}zIOMI*W`Q`fq}zYS2cKoE6z>;qK*N6gWEiv?XE|;Nj@JV z$49U9v^BlCFyg#Fa5-Pz{HA@??H#{;;rM63l`LT7;cbUvg1SLDR$P7Q!me!>hwvjyaf!F$UB)Am>vC}!YQ1}SjKT06PU~trZSD`%wQ(7 zIg_)Q&p9k(IcvC>OIXV~*0X_)Y+^H)vz2XJ$=kS^w{s2Gas%(+oxF=1xtTx5YxoO} z;SCg{1huGx2j!fLC1^km=HLc+qj3fs;Kh?%AvFp`1&-iThO-D?VHYPb1{ax#ucg!l z>_95s#vOPRr*NLcjrbA8N_e;gSuCLC%q(Vcs9*RkM=bf^Vt9CCL^g{RZDM zg?A;FE9pV_Rw!gjwkocdY*V~Za;4&A$yG|a7QS}CsSFy&t4>EyOnAS%vsS?^xlXaz z*&h_cb;R8uoy%s&rq~KWZB6)R>lKzM9UWHD{EsFCb zdlZu-f2o)&DRakSj^wWu?G^Sa&XU}wSSYz&F;CI~Cff&DWboamut)NK#p#ke6blXv zduo6zL70(B79-3kCCd_Kw2}o1BO}q0r3xdX%92G36Ru?W!bB)p$S`A-ENPfXC5szo zoRVb@69stfgRFR%8p8zwPZ5D#3-2&FfvaqnHDe;mCOy8o0Lot zn46W%5|~L!rivFvn!}O-12b94sDZgf$;hd5bQ|WJN#86h(i@RK)?3X^I0SrPvk& zB&8EA21=$Y4wCd{$b;Yz$xOwel39wulG%#GBy$u;NX}Gt7?@neP)TV~i?*+3E84!2 z7PV;mDqneN8>>JuQgV)>ZIro+(j!KC-J?(iuUu3*LgvEfGvZdJ)ld`9m{J=58gw`Y`t>`Td8vSIJHQW{Z+N0n9I)=!j&_b*dxEdB)in5z7_M zdl9kTBl6ip3ZF{uR1BAVSji>?lQ}XH=h4Yg8mF!Poo>j6_fjOXL&jQn@ zV4Ud4*m!#z{-|gh=}(HbI}a+_4t`Ow#p#L+3m+q|4lCL3z`U&Rv9mui%xlYeRN1|? zc}3Bd_Ekl@++S0)%iWew&@T7a6{{s}DFqiu+ENNGlsv9vR|WH?lKmCT3E5}N9ycQV zGkJbep+~Y`vD0ZE*T=EWgs41z?zBe5@TAil6*jhAKKCh{lQhb1x%!@q+C=Y;*tg;) z#5KlkiSxy~;-li9i$4*6X#H2%RTE?vkuMrYIfo5KjgXcCg&C9tG`q^Wlxu#EsrjLr@~dSx#IBrN%KqQ_f(>CMrBv!(aLL8xmCSY&sANk-cfyN z;ia0rHK%GWFW$I#_u`X_FDyw}(o;LBw!U_C?dIC;wY%$FbvbpL>rQw=JexiHJm1%M zHAFTPHtcLT)i|`VzHx8k7fr*PmN)e@9c#MWT-4keyfkjtR$oZFfIzuHXCr4o}|iz25tNp7&|n;k)yB z->qSufdIDva8=e-H28E*jRd+q00tgkT)U>Gd{=WNFyc6Hd7QhtqH5mHb{ELl8zR9i zA9PvJ<)FNl?=i|<*Vz2#{2#uT^W6dbde$zltT=Mvb~_Mq1n}{%t7vZMvX|@S`#~~3 zw7#OQ`t6e7o4&wTx8=io4a-+HMn1Q18F23gFyMFFj)w(q1ZAwqNd0-rwmZ?>=!EbP zy&adhqN`Kyd(?T@t$%#`qfzkL0G~&%#z_VMzU}b!DVD$QBmaE>Z@YJk_pJB4>27k2 z*U9joP<@KM*%L3~d(OdU9EYonLJFHXf=_TPS}`79qY;O25edw}67*ve+R?=BxP^b> zI$byb2QRUNlhMpsjAJ^}7|#sOEwaui2%3?KI}oKJE*BN)kO#xQ{sIFX5*!Xzd$l~Xy5 znVi8q=5ql{xsYWnX9X9rl2xqc5-wvc>)61jxPpyb#WiftD~()AU$!?D)NsTrBz(&QQK6=Yk^ae zixhiH%G9u=f#9uD(n|1FE67yv)+lK&co!>tCh1nva_}xu*|;h%mMZB(cx5VBNRg~n zY?iE393fe+_=Mzg#j#FtU>Lu4TI6`q*%cVX9OtOKZgQ>#hDCRmQLRe47+#N(eulSA z;kx8{MVAxNE7DUU?=~svb$EZIV6R8!xW!qLn-%3|dVj2#F1bZ9P4cIT@se8=GbFbu z&XnA)I9<{K!tFsO6ui$V?2&w4agyW?#jFm$-oNQ8^9^Qzl9>ndn36dNBWs=|vk@jp z$-IP-b;*($3Nu*AT!jf%GHYRmD4D-7LzT>Am|;rhG)xHKu?LynFvFEhbeK>jQyxZ& zXUXJ;8L4C$fC*EwAi#`LvNXVqR(A0u@$vOoSYe`edk_8i|WbuNL za$ERLGC`4&6BN5hPE_=fOjPuhlnPtyDk<$}(NA)UVmHYoMSn?8vK#~hB&BUE_K-|f z43tb$>?Ju>v5#cBVqZz=ON;#^rz!TAl)kiRdn{AY_E?tk&^Fl&#UYZ}ineK_!7bXR zkx64w`o+vvv^|rf7%DkOag=19qV0!#McWSrinbpLZT$=7q_*xwini{>ini8s6>Y8O zDcV}kSG4slQMC14plC~1s%T5MP|=pIOfgaFZ^{*Ija-VhMiq*-M2i${St=E6DXJ9h zEw5Izx4A~q-r2>9_Kvs}?HySnJaVAD;-!lAikB(cE3Q?vS6ruPFSuUOUgUB`dyx%_ z_9CBBv=_NT(O%?AMfq)yX_PmD_D5GK+8=FFv_HC9(Vo0n(Vl#bqCI(wqCMYQMSH$= zigxVNigs-4BbA^XY|9{M2e&EO!RwXXjhPKf9^xB5(hj~`XBOYdx!Z{}( zSRV3DnH)cG4hKhasqodPp^3;dft>pO+^OA!7>{p6ml6#cw z4q)~w7|EBF>=1N z6H2x;F!mM-+1$XKR9GN+O3~K%G*Dv?Y?C){D0(Dq?F421o3|A08gNF@t^sEi?HX`S zah~LP#rcvK6bqb5!@_vSDH|5V)lSo}4hB2nA(_1EtOyAk{jQ9x}n{pBmp1e=PoPLTExxLVLo#1n-1?6LwCxJaN{<9f>W8hZAp3%AQm|>GUod1!-pv}D<<9y^*6FN= zGfHM`nQ<<=F#E*Ji8I?~-k%jQ%RTG#Z2#Gfv$xNFEoWEGrJP&2J#&k5n{xN(-kGy* z&aOFE^9JQj$#dsz&hwniFV5ebe>DGL!I6UNg=K}mDDo{DS5#BfSv;t?w0KwXwYi~l zYv*pAdu`r|c^679l(v-~D800>WMS*VR~Mcx8&FnL?pvN*o>N{{USHl?e%%$~Ds%03 z-K{9AXs@`ksC3bT%CVI#l?N(sS0z`qRlQSnr+RvIP4(XDOEuv&S&MrHEMDghaqnc9X1 diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 26792a1bd03db..c1084bbfd7646 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -551,6 +551,7 @@ export class Codicon implements CSSIcon { public static readonly azureDevops = new Codicon('azure-devops', { fontCharacter: '\\ebe8' }); public static readonly verifiedFilled = new Codicon('verified-filled', { fontCharacter: '\\ebe9' }); public static readonly newLine = new Codicon('newline', { fontCharacter: '\\ebea' }); + public static readonly layout = new Codicon('layout', { fontCharacter: '\\ebeb' }); // derived icons, that could become separate icons diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 75bb1d6a0643b..9289a640ca37f 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -49,7 +49,7 @@ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -const layoutControlIcon = registerIcon('layout-control', Codicon.editorLayout, localize('layoutControlIcon', "Icon for the layout control menu found in the title bar.")); +const layoutControlIcon = registerIcon('layout-control', Codicon.layout, localize('layoutControlIcon', "Icon for the layout control menu found in the title bar.")); export class TitlebarPart extends Part implements ITitleService { From 2c9f5fe0b74066726568919500240d87d23cc020 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 1 Dec 2021 20:54:47 +0100 Subject: [PATCH 0239/2210] Emit `onSocketTimeout` at most once every 20s --- src/vs/base/parts/ipc/common/ipc.net.ts | 15 +++++- .../base/parts/ipc/test/node/ipc.net.test.ts | 54 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 0caf27ec88b91..c62b08b0f6436 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -778,6 +778,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { private _incomingAckTimeout: any | null; private _lastReplayRequestTime: number; + private _lastSocketTimeoutTime: number; private _socket: ISocket; private _socketWriter: ProtocolWriter; @@ -819,6 +820,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._incomingAckTimeout = null; this._lastReplayRequestTime = 0; + this._lastSocketTimeoutTime = Date.now(); this._socketDisposables = []; this._socket = socket; @@ -887,6 +889,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socket.dispose(); this._lastReplayRequestTime = 0; + this._lastSocketTimeoutTime = Date.now(); this._socket = socket; this._socketWriter = new ProtocolWriter(this._socket); @@ -1063,10 +1066,12 @@ export class PersistentProtocol implements IMessagePassingProtocol { const oldestUnacknowledgedMsg = this._outgoingUnackMsg.peek()!; const timeSinceOldestUnacknowledgedMsg = Date.now() - oldestUnacknowledgedMsg.writtenTime; const timeSinceLastReceivedSomeData = Date.now() - this._socketReader.lastReadTime; + const timeSinceLastTimeout = Date.now() - this._lastSocketTimeoutTime; if ( timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.TimeoutTime && timeSinceLastReceivedSomeData >= ProtocolConstants.TimeoutTime + && timeSinceLastTimeout >= ProtocolConstants.TimeoutTime ) { // It's been a long time since our sent message was acknowledged // and a long time since we received some data @@ -1074,15 +1079,23 @@ export class PersistentProtocol implements IMessagePassingProtocol { // But this might be caused by the event loop being busy and failing to read messages if (!this._loadEstimator.hasHighLoad()) { // Trash the socket + this._lastSocketTimeoutTime = Date.now(); this._onSocketTimeout.fire(undefined); return; } } + const minimumTimeUntilTimeout = Math.max( + ProtocolConstants.TimeoutTime - timeSinceOldestUnacknowledgedMsg, + ProtocolConstants.TimeoutTime - timeSinceLastReceivedSomeData, + ProtocolConstants.TimeoutTime - timeSinceLastTimeout, + 500 + ); + this._outgoingAckTimeout = setTimeout(() => { this._outgoingAckTimeout = null; this._recvAckCheck(); - }, Math.max(ProtocolConstants.TimeoutTime - timeSinceOldestUnacknowledgedMsg, 500)); + }, minimumTimeUntilTimeout); } private _sendAck(): void { diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 6e913058854b0..50c3d1428d934 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -9,7 +9,7 @@ import { createServer, Socket } from 'net'; import { tmpdir } from 'os'; import { Barrier, timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; @@ -431,6 +431,58 @@ suite('PersistentProtocol reconnection', () => { ); }); + test('onSocketTimeout is emitted at most once every 20s', async () => { + await runWithFakedTimers( + { + useFakeTimers: true, + useSetImmediate: true, + maxTaskCount: 1000 + }, + async () => { + + const loadEstimator: ILoadEstimator = { + hasHighLoad: () => false + }; + const ether = new Ether(); + const aSocket = new NodeSocket(ether.a); + const a = new PersistentProtocol(aSocket, null, loadEstimator); + const aMessages = new MessageStream(a); + const bSocket = new NodeSocket(ether.b); + const b = new PersistentProtocol(bSocket, null, loadEstimator); + const bMessages = new MessageStream(b); + + // never receive acks + b.pauseSocketWriting(); + + // send message a1 to have something unacknowledged + a.send(VSBuffer.fromString('a1')); + + // wait for the first timeout to fire + await Event.toPromise(a.onSocketTimeout); + + let timeoutFiredAgain = false; + const timeoutListener = a.onSocketTimeout(() => { + timeoutFiredAgain = true; + }); + + // send more messages + a.send(VSBuffer.fromString('a2')); + a.send(VSBuffer.fromString('a3')); + + // wait for 10s + await timeout(ProtocolConstants.TimeoutTime / 2); + + assert.strictEqual(timeoutFiredAgain, false); + + timeoutListener.dispose(); + aMessages.dispose(); + bMessages.dispose(); + a.dispose(); + b.dispose(); + } + ); + }); + test('writing can be paused', async () => { await runWithFakedTimers({ useFakeTimers: true, maxTaskCount: 100 }, async () => { const loadEstimator: ILoadEstimator = { From 9415001363cf3776652eaeaf796cc17c7073a788 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 1 Dec 2021 21:52:20 +0100 Subject: [PATCH 0240/2210] Disable Nagle's algorithm for web sockets --- src/vs/platform/remote/common/remoteAgentConnection.ts | 2 +- src/vs/platform/remote/node/nodeSocketFactory.ts | 4 +++- src/vs/server/remoteExtensionHostAgentServer.ts | 2 ++ .../services/extensions/node/extensionHostProcessSetup.ts | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 7729501248f05..5543fdd80c195 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -573,7 +573,7 @@ export abstract class PersistentConnection extends Disposable { })); this._register(protocol.onSocketTimeout(() => { const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true); - this._options.logService.trace(`${logPrefix} received socket timeout event.`); + this._options.logService.info(`${logPrefix} received socket timeout event.`); this._beginReconnecting(); })); diff --git a/src/vs/platform/remote/node/nodeSocketFactory.ts b/src/vs/platform/remote/node/nodeSocketFactory.ts index 4f5a2c938a130..6b00b907f70e8 100644 --- a/src/vs/platform/remote/node/nodeSocketFactory.ts +++ b/src/vs/platform/remote/node/nodeSocketFactory.ts @@ -21,7 +21,7 @@ export const nodeSocketFactory = new class implements ISocketFactory { } const nonce = buffer.toString('base64'); - let headers = [ + const headers = [ `GET ws://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=true HTTP/1.1`, `Connection: Upgrade`, `Upgrade: websocket`, @@ -39,6 +39,8 @@ export const nodeSocketFactory = new class implements ISocketFactory { }; socket.on('data', onData); }); + // Disable Nagle's algorithm. + socket.setNoDelay(true); socket.once('error', errorListener); } }; diff --git a/src/vs/server/remoteExtensionHostAgentServer.ts b/src/vs/server/remoteExtensionHostAgentServer.ts index 7180765ad62d9..93f874c604b75 100644 --- a/src/vs/server/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/remoteExtensionHostAgentServer.ts @@ -497,6 +497,8 @@ export class RemoteExtensionHostAgentServer extends Disposable { // Never timeout this socket due to inactivity! socket.setTimeout(0); + // Disable Nagle's algorithm + socket.setNoDelay(true); // Finally! if (skipWebSocketFrames) { diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index e9a12654d26e7..495fe601cc4f6 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -120,6 +120,10 @@ function _createExtHostProtocol(): Promise { process.on('message', (msg: IExtHostSocketMessage | IExtHostReduceGraceTimeMessage, handle: net.Socket) => { if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') { + // Disable Nagle's algorithm. We also do this on the server process, + // but nodejs doesn't document if this option is transferred with the socket + handle.setNoDelay(true); + const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64')); let socket: NodeSocket | WebSocketNodeSocket; if (msg.skipWebSocketFrames) { From 821e05d3213322120b8391143dae33114642c443 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 1 Dec 2021 16:02:49 -0500 Subject: [PATCH 0241/2210] Fix #138214 --- .../telemetry/test/electron-browser/commonProperties.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts b/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts index fed7bd8c4ffee..ac59653494029 100644 --- a/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts @@ -70,7 +70,7 @@ suite('Telemetry - common properties', function () { assert.ok(!('common.source' in props_1)); }); - test('lastSessionDate when aviablale', async function () { + test('lastSessionDate when available', async function () { testStorageService.store('telemetry.lastSessionDate', new Date().toUTCString(), StorageScope.GLOBAL, StorageTarget.MACHINE); From aed641a0dddca30426c733d5468dba90d7d4c76c Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Wed, 1 Dec 2021 13:12:31 -0800 Subject: [PATCH 0242/2210] Cloud backup instrumentation Co-authored-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> --- .../diagnostics/node/diagnosticsService.ts | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 72971fa7795f6..152250bc22605 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -38,6 +38,11 @@ interface ConfigFilePatterns { relativePathPattern?: RegExp; } +interface RootFileMatcher { + tag: string; + matcher: (path: string) => boolean; +} + export async function collectWorkspaceStats(folder: string, filter: string[]): Promise { const configFilePatterns: ConfigFilePatterns[] = [ { tag: 'grunt.js', filePattern: /^gruntfile\.js$/i }, @@ -61,12 +66,83 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P { tag: 'dockerfile', filePattern: /^(dockerfile|docker\-compose\.ya?ml)$/i } ]; + let rootFileMatchers: RootFileMatcher[]; + + // Linux is omitted because few cloud sync clients support it, and for those who are available on Linux, there are multiple clients and they can be configured differently + const homeDir = osLib.homedir().toLowerCase(); + switch (process.platform) { + case 'win32': + rootFileMatchers = [ + { + tag: 'gdrive', matcher: (path) => { + // File Streaming or Mirror Files mode + return /^[a-z]:\\(my drive|shared drives)\\/.test(path) || path.startsWith(homeDir + '\\my drive\\'); + } + }, + { + tag: 'dropbox', matcher: path => path.startsWith(homeDir + '\\dropbox') // Ending in * + }, + { + tag: 'onedrive', matcher: path => path.startsWith(homeDir + '\\onedrive') // Ending in * + }, + { + tag: 'box', matcher: path => path.startsWith(homeDir + '\\box\\') + }, + { + tag: 'nextcloud', matcher: path => path.startsWith(homeDir + '\\nextcloud\\') + }, + { + tag: 'owncloud', matcher: path => path.startsWith(homeDir + '\\owncloud\\') + }, + ]; + break; + + case 'darwin': + rootFileMatchers = [ + { + tag: 'gdrive', matcher: (path) => { + // File Streaming mode + return path.startsWith('/volumes/googledrive/') || path.startsWith(homeDir + '/my drive/'); + } + }, + { + tag: 'dropbox', matcher: path => path.startsWith(homeDir + '/dropbox') // Ending in * + }, + { + tag: 'onedrive', matcher: (path) => { + // Old vs new client + return path.startsWith(homeDir + '/onedrive') || path.startsWith(homeDir + '/library/cloudstorage/onedrive'); + } + }, + { + tag: 'icloud', matcher: path => path.startsWith(homeDir + '/library/mobile documents/') + }, + { + tag: 'box', matcher: path => path.startsWith(homeDir + '/box/') + }, + { + tag: 'nextcloud', matcher: path => path.startsWith(homeDir + '/nextcloud/') + }, + { + tag: 'owncloud', matcher: path => path.startsWith(homeDir + '/owncloud/') + }, + ]; + break; + } + const fileTypes = new Map(); const configFiles = new Map(); const MAX_FILES = 20000; function collect(root: string, dir: string, filter: string[], token: { count: number, maxReached: boolean }): Promise { + for (const rootPath of rootFileMatchers) { + const lowercaseRoot = root.toLowerCase(); + if (rootPath.matcher(lowercaseRoot)) { + configFiles.set(rootPath.tag, 1); + } + } + const relativePath = dir.substring(root.length + 1); return Promises.withAsyncBody(async resolve => { From 9057a71b61db7a4690059236421dadd7bbb3efa1 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 22:18:05 +0100 Subject: [PATCH 0243/2210] Overriding color theme is not picked when changed. Fixes #138160 --- .../themes/browser/workbenchThemeService.ts | 156 ++++++++++-------- 1 file changed, 85 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 09ef85d453a1c..08073f0ea6538 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -426,33 +426,38 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public setColorTheme(themeIdOrTheme: string | undefined | IWorkbenchColorTheme, settingsTarget: ThemeSettingTarget): Promise { return this.colorThemeSequencer.queue(async () => { - if (!themeIdOrTheme) { - return null; - } - const themeId = types.isString(themeIdOrTheme) ? validateThemeId(themeIdOrTheme) : themeIdOrTheme.id; - if (this.currentColorTheme.isLoaded && themeId === this.currentColorTheme.id) { - if (settingsTarget !== 'preview') { - this.currentColorTheme.toStorage(this.storageService); - } - return this.settings.setColorTheme(this.currentColorTheme, settingsTarget); - } + return this.internalSetColorTheme(themeIdOrTheme, settingsTarget); + }); + } - let themeData = this.colorThemeRegistry.findThemeById(themeId); - if (!themeData) { - if (themeIdOrTheme instanceof ColorThemeData) { - themeData = themeIdOrTheme; - } else { - return null; - } + private async internalSetColorTheme(themeIdOrTheme: string | undefined | IWorkbenchColorTheme, settingsTarget: ThemeSettingTarget): Promise { + if (!themeIdOrTheme) { + return null; + } + const themeId = types.isString(themeIdOrTheme) ? validateThemeId(themeIdOrTheme) : themeIdOrTheme.id; + if (this.currentColorTheme.isLoaded && themeId === this.currentColorTheme.id) { + if (settingsTarget !== 'preview') { + this.currentColorTheme.toStorage(this.storageService); } - try { - await themeData.ensureLoaded(this.extensionResourceLoaderService); - themeData.setCustomizations(this.settings); - return this.applyTheme(themeData, settingsTarget); - } catch (error) { - throw new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location?.toString(), error.message)); + return this.settings.setColorTheme(this.currentColorTheme, settingsTarget); + } + + let themeData = this.colorThemeRegistry.findThemeById(themeId); + if (!themeData) { + if (themeIdOrTheme instanceof ColorThemeData) { + themeData = themeIdOrTheme; + } else { + return null; } - }); + } + try { + await themeData.ensureLoaded(this.extensionResourceLoaderService); + themeData.setCustomizations(this.settings); + return this.applyTheme(themeData, settingsTarget); + } catch (error) { + throw new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location?.toString(), error.message)); + } + } private reloadCurrentColorTheme() { @@ -474,7 +479,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { const theme = this.colorThemeRegistry.findThemeBySettingsId(settingId); if (theme) { if (settingId !== this.currentColorTheme.settingsId) { - await this.setColorTheme(theme.id, undefined); + await this.internalSetColorTheme(theme.id, undefined); } else if (theme !== this.currentColorTheme) { theme.setCustomizations(this.settings); await this.applyTheme(theme, undefined, true); @@ -589,34 +594,38 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public async setFileIconTheme(iconThemeOrId: string | undefined | IWorkbenchFileIconTheme, settingsTarget: ThemeSettingTarget): Promise { return this.fileIconThemeSequencer.queue(async () => { - if (iconThemeOrId === undefined) { - iconThemeOrId = ''; - } - const themeId = types.isString(iconThemeOrId) ? iconThemeOrId : iconThemeOrId.id; - if (themeId !== this.currentFileIconTheme.id || !this.currentFileIconTheme.isLoaded) { + return this.internalSetFileIconTheme(iconThemeOrId, settingsTarget); + }); + } - let newThemeData = this.fileIconThemeRegistry.findThemeById(themeId); - if (!newThemeData && iconThemeOrId instanceof FileIconThemeData) { - newThemeData = iconThemeOrId; - } - if (!newThemeData) { - newThemeData = FileIconThemeData.noIconTheme; - } - await newThemeData.ensureLoaded(this.extensionResourceLoaderService); + private async internalSetFileIconTheme(iconThemeOrId: string | undefined | IWorkbenchFileIconTheme, settingsTarget: ThemeSettingTarget): Promise { + if (iconThemeOrId === undefined) { + iconThemeOrId = ''; + } + const themeId = types.isString(iconThemeOrId) ? iconThemeOrId : iconThemeOrId.id; + if (themeId !== this.currentFileIconTheme.id || !this.currentFileIconTheme.isLoaded) { - this.applyAndSetFileIconTheme(newThemeData); // updates this.currentFileIconTheme + let newThemeData = this.fileIconThemeRegistry.findThemeById(themeId); + if (!newThemeData && iconThemeOrId instanceof FileIconThemeData) { + newThemeData = iconThemeOrId; + } + if (!newThemeData) { + newThemeData = FileIconThemeData.noIconTheme; } + await newThemeData.ensureLoaded(this.extensionResourceLoaderService); - const themeData = this.currentFileIconTheme; + this.applyAndSetFileIconTheme(newThemeData); // updates this.currentFileIconTheme + } - // remember theme data for a quick restore - if (themeData.isLoaded && settingsTarget !== 'preview' && (!themeData.location || !getRemoteAuthority(themeData.location))) { - themeData.toStorage(this.storageService); - } - await this.settings.setFileIconTheme(this.currentFileIconTheme, settingsTarget); + const themeData = this.currentFileIconTheme; - return themeData; - }); + // remember theme data for a quick restore + if (themeData.isLoaded && settingsTarget !== 'preview' && (!themeData.location || !getRemoteAuthority(themeData.location))) { + themeData.toStorage(this.storageService); + } + await this.settings.setFileIconTheme(this.currentFileIconTheme, settingsTarget); + + return themeData; } public async getMarketplaceFileIconThemes(publisher: string, name: string, version: string): Promise { @@ -645,7 +654,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { const theme = this.fileIconThemeRegistry.findThemeBySettingsId(settingId); if (theme) { if (settingId !== this.currentFileIconTheme.settingsId) { - await this.setFileIconTheme(theme.id, undefined); + await this.internalSetFileIconTheme(theme.id, undefined); } else if (theme !== this.currentFileIconTheme) { this.applyAndSetFileIconTheme(theme, true); } @@ -691,32 +700,37 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public async setProductIconTheme(iconThemeOrId: string | undefined | IWorkbenchProductIconTheme, settingsTarget: ThemeSettingTarget): Promise { return this.productIconThemeSequencer.queue(async () => { - if (iconThemeOrId === undefined) { - iconThemeOrId = ''; - } - const themeId = types.isString(iconThemeOrId) ? iconThemeOrId : iconThemeOrId.id; - if (themeId !== this.currentProductIconTheme.id || !this.currentProductIconTheme.isLoaded) { - let newThemeData = this.productIconThemeRegistry.findThemeById(themeId); - if (!newThemeData && iconThemeOrId instanceof ProductIconThemeData) { - newThemeData = iconThemeOrId; - } - if (!newThemeData) { - newThemeData = ProductIconThemeData.defaultTheme; - } - await newThemeData.ensureLoaded(this.extensionResourceLoaderService, this.logService); + return this.internalSetProductIconTheme(iconThemeOrId, settingsTarget); + }); + } - this.applyAndSetProductIconTheme(newThemeData); // updates this.currentProductIconTheme + private async internalSetProductIconTheme(iconThemeOrId: string | undefined | IWorkbenchProductIconTheme, settingsTarget: ThemeSettingTarget): Promise { + if (iconThemeOrId === undefined) { + iconThemeOrId = ''; + } + const themeId = types.isString(iconThemeOrId) ? iconThemeOrId : iconThemeOrId.id; + if (themeId !== this.currentProductIconTheme.id || !this.currentProductIconTheme.isLoaded) { + let newThemeData = this.productIconThemeRegistry.findThemeById(themeId); + if (!newThemeData && iconThemeOrId instanceof ProductIconThemeData) { + newThemeData = iconThemeOrId; } - const themeData = this.currentProductIconTheme; - - // remember theme data for a quick restore - if (themeData.isLoaded && settingsTarget !== 'preview' && (!themeData.location || !getRemoteAuthority(themeData.location))) { - themeData.toStorage(this.storageService); + if (!newThemeData) { + newThemeData = ProductIconThemeData.defaultTheme; } - await this.settings.setProductIconTheme(this.currentProductIconTheme, settingsTarget); + await newThemeData.ensureLoaded(this.extensionResourceLoaderService, this.logService); + + this.applyAndSetProductIconTheme(newThemeData); // updates this.currentProductIconTheme + } + const themeData = this.currentProductIconTheme; + + // remember theme data for a quick restore + if (themeData.isLoaded && settingsTarget !== 'preview' && (!themeData.location || !getRemoteAuthority(themeData.location))) { + themeData.toStorage(this.storageService); + } + await this.settings.setProductIconTheme(this.currentProductIconTheme, settingsTarget); + + return themeData; - return themeData; - }); } public async getMarketplaceProductIconThemes(publisher: string, name: string, version: string): Promise { @@ -745,7 +759,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { const theme = this.productIconThemeRegistry.findThemeBySettingsId(settingId); if (theme) { if (settingId !== this.currentProductIconTheme.settingsId) { - await this.setProductIconTheme(theme.id, undefined); + await this.internalSetProductIconTheme(theme.id, undefined); } else if (theme !== this.currentProductIconTheme) { this.applyAndSetProductIconTheme(theme, true); } From 45b349993b8f5009c3f82736250a3e7369a0ee78 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 1 Dec 2021 18:41:54 +0000 Subject: [PATCH 0244/2210] GH Actions workflows can use .yaml too --- src/vs/platform/diagnostics/node/diagnosticsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 152250bc22605..cbeeb6173cab3 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -61,7 +61,7 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P { tag: 'sln', filePattern: /^.+\.sln$/i }, { tag: 'csproj', filePattern: /^.+\.csproj$/i }, { tag: 'cmake', filePattern: /^.+\.cmake$/i }, - { tag: 'github-actions', filePattern: /^.+\.yml$/i, relativePathPattern: /^\.github(?:\/|\\)workflows$/i }, + { tag: 'github-actions', filePattern: /^.+\.ya?ml$/i, relativePathPattern: /^\.github(?:\/|\\)workflows$/i }, { tag: 'devcontainer.json', filePattern: /^devcontainer\.json$/i }, { tag: 'dockerfile', filePattern: /^(dockerfile|docker\-compose\.ya?ml)$/i } ]; From 86456bddabf845ec841eb50bcc144f45af773c87 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 22:30:08 +0100 Subject: [PATCH 0245/2210] Cannot read property 'theme' of undefined: TypeError: Cannot read property 'theme' of undefined. Fixes #138138 --- src/vs/workbench/contrib/themes/browser/themes.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index d4a7d66c1ee66..db4efe86cd3ab 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -312,7 +312,7 @@ class InstalledThemesPicker { quickpick.hide(); s(); }); - quickpick.onDidChangeActive(themes => selectTheme(themes[0].theme, false)); + quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false)); quickpick.onDidHide(() => { if (!isCompleted) { selectTheme(currentTheme, true); From 40cb966b4cf3c8ef04f9717d49ed4923b6cbc803 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Dec 2021 22:35:47 +0100 Subject: [PATCH 0246/2210] List of themes from MP flash after more themes arrive. Fixes #138137 --- .../workbench/contrib/themes/browser/themes.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index db4efe86cd3ab..f7554cfea4547 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -99,9 +99,9 @@ class MarketplaceThemesPicker { try { const installedExtensions = await this._installedExtensions; - const options = { text: `${this.marketplaceQuery} ${value}`, pageSize: 20 }; + const options = { text: `${this.marketplaceQuery} ${value}`, pageSize: 40 }; const pager = await this.extensionGalleryService.query(options, token); - for (let i = 0; i < pager.total && i < 2; i++) { + for (let i = 0; i < pager.total && i < 1; i++) { if (token.isCancellationRequested) { break; } From 112ca126f6f86387f6054e8d55544d97f72f34ef Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 22:36:15 +0100 Subject: [PATCH 0247/2210] Fix #138233 --- .../extensions/browser/extensionEditor.ts | 62 ++++++++++++++++--- .../browser/extensions.contribution.ts | 2 + .../extensions/browser/extensionsList.ts | 2 +- .../extensions/browser/extensionsWidgets.ts | 13 +--- .../browser/media/extensionEditor.css | 3 +- 5 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index d05387b31377b..c7652013e1515 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -23,7 +23,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, IExtension, ExtensionContainers, ExtensionEditorTab, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions'; -import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, PreReleaseIndicatorWidget, ExtensionHoverWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; +import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, ExtensionHoverWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { @@ -72,6 +72,7 @@ import { errorIcon, infoIcon, preReleaseIcon, starEmptyIcon, verifiedPublisherIc import { MarkdownString } from 'vs/base/common/htmlContent'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; class NavBar extends Disposable { @@ -164,6 +165,10 @@ const enum WebviewIndex { const CONTEXT_SHOW_PRE_RELEASE_VERSION = new RawContextKey('showPreReleaseVersion', false); +interface IExtensionEditorWidget extends IDisposable { + updateInput(extension: IExtension, gallery: IGalleryExtension | undefined, preserveFocus?: boolean): void; +} + export class ExtensionEditor extends EditorPane { static readonly ID: string = 'workbench.editor.extension'; @@ -189,12 +194,14 @@ export class ExtensionEditor extends EditorPane { private dimension: Dimension | undefined; private showPreReleaseVersionContextKey: IContextKey | undefined; + private readonly extensionEditorWidget: IExtensionEditorWidget; constructor( @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IThemeService themeService: IThemeService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, @@ -213,6 +220,29 @@ export class ExtensionEditor extends EditorPane { this.extensionReadme = null; this.extensionChangelog = null; this.extensionManifest = null; + const that = this; + this.extensionEditorWidget = this._register(new class extends Disposable implements IExtensionEditorWidget { + private gallery: IGalleryExtension | undefined = undefined; + private extension: IExtension | undefined = undefined; + constructor() { + super(); + this._register(that.extensionsWorkbenchService.onChange(e => { + if (e && this.extension + && areSameExtensions(this.extension.identifier, e?.identifier) + && (!this.extension.server || this.extension.server === e.server) + && this.extension.latestVersion !== e.latestVersion) { + this.updateInput(e, this.gallery); + } + })); + } + updateInput(extension: IExtension, gallery: IGalleryExtension | undefined, preserveFocus?: boolean): void { + this.extension = extension; + this.gallery = gallery; + if (that.template) { + that.updateTemplate(this.extension, this.gallery, that.template, !!preserveFocus); + } + } + }()); } override get scopedContextKeyService(): IContextKeyService | undefined { @@ -242,6 +272,7 @@ export class ExtensionEditor extends EditorPane { preview.textContent = localize('preview', "Preview"); const preRelease = append(title, $('span.pre-release')); + preRelease.textContent = localize('preRelease', "Pre-Release"); const builtin = append(title, $('span.builtin')); builtin.textContent = localize('builtin', "Built-in"); @@ -329,7 +360,8 @@ export class ExtensionEditor extends EditorPane { await super.setInput(input, options, context, token); this.updatePreReleaseVersionContext(); if (this.template) { - await this.updateTemplate(input.extension, this.template, !!options?.preserveFocus); + const gallery = await this.getGallery(input.extension, options?.showPreReleaseVersion); + this.extensionEditorWidget.updateInput(input.extension, gallery, options?.preserveFocus); } } @@ -337,9 +369,25 @@ export class ExtensionEditor extends EditorPane { const currentOptions: IExtensionEditorOptions | undefined = this.options; super.setOptions(options); this.updatePreReleaseVersionContext(); - if (currentOptions?.showPreReleaseVersion !== options?.showPreReleaseVersion) { - this.openTab(ExtensionEditorTab.Readme); + if (this.input && currentOptions?.showPreReleaseVersion !== options?.showPreReleaseVersion) { + this.getGallery((this.input).extension, !!options?.showPreReleaseVersion).then(gallery => this.extensionEditorWidget.updateInput((this.input).extension, gallery)); + } + } + + private async getGallery(extension: IExtension, preRelease?: boolean): Promise { + if (isUndefined(preRelease)) { + return undefined; + } + if (preRelease === extension.gallery?.properties.isPreReleaseVersion) { + return undefined; + } + if (preRelease && !extension.hasPreReleaseVersion) { + return undefined; + } + if (!preRelease && !extension.hasReleaseVersion) { + return undefined; } + return (await this.extensionGalleryService.query({ includePreRelease: preRelease, names: [extension.identifier.id] }, CancellationToken.None)).firstPage[0]; } private updatePreReleaseVersionContext(): void { @@ -363,7 +411,7 @@ export class ExtensionEditor extends EditorPane { } } - private async updateTemplate(extension: IExtension, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise { + private async updateTemplate(extension: IExtension, gallery: IGalleryExtension | undefined, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise { this.activeElement = null; this.editorLoadComplete = false; @@ -383,8 +431,9 @@ export class ExtensionEditor extends EditorPane { template.icon.src = extension.iconUrl; template.name.textContent = extension.displayName; - template.version.textContent = `v${extension.version}`; + template.version.textContent = `v${gallery ? gallery.version : extension.version}`; template.preview.style.display = extension.preview ? 'inherit' : 'none'; + template.preRelease.style.display = (gallery ? gallery.properties.isPreReleaseVersion : (extension.local?.isPreReleaseVersion || extension.gallery?.properties.isPreReleaseVersion)) ? 'inherit' : 'none'; template.builtin.style.display = extension.isBuiltin ? 'inherit' : 'none'; template.description.textContent = extension.description; @@ -429,7 +478,6 @@ export class ExtensionEditor extends EditorPane { } const widgets = [ - this.instantiationService.createInstance(PreReleaseIndicatorWidget, template.preRelease, { label: true, icon: false, enableOnlyForInstalled: false }), remoteBadge, this.instantiationService.createInstance(InstallCountWidget, template.installCount, false), this.instantiationService.createInstance(RatingsWidget, template.rating, false) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index bcd803bef00c0..a23606d66e8b4 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1208,6 +1208,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); if (extension) { + extensionWorkbenchService.open(extension, { showPreReleaseVersion: true }); await extensionWorkbenchService.install(extension, { installPreReleaseVersion: true }); } } @@ -1225,6 +1226,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); if (extension) { + extensionWorkbenchService.open(extension, { showPreReleaseVersion: false }); await extensionWorkbenchService.install(extension, { installPreReleaseVersion: false }); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index a9d0df174310b..7a91c7bd0deb3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -138,7 +138,7 @@ export class Renderer implements IPagedRenderer { extensionPackBadgeWidget, headerRemoteBadgeWidget, extensionHoverWidget, - this.instantiationService.createInstance(PreReleaseIndicatorWidget, preRelease, { icon: true, label: false, enableOnlyForInstalled: true }), + this.instantiationService.createInstance(PreReleaseIndicatorWidget, preRelease), this.instantiationService.createInstance(SyncIgnoredWidget, syncIgnore), this.instantiationService.createInstance(ExtensionActivationStatusWidget, activationStatus, true), this.instantiationService.createInstance(InstallCountWidget, installCount, true), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 538b2d417a206..f80a7ed0efa0c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -162,7 +162,6 @@ export class PreReleaseIndicatorWidget extends ExtensionWidget { constructor( private readonly container: HTMLElement, - private readonly options: { label: boolean, icon: boolean, enableOnlyForInstalled: boolean }, ) { super(); container.classList.add('extension-pre-release'); @@ -180,16 +179,11 @@ export class PreReleaseIndicatorWidget extends ExtensionWidget { return; } - if (this.options.enableOnlyForInstalled && this.extension.state !== ExtensionState.Installed) { + if (this.extension.state !== ExtensionState.Installed) { return; } - if (this.options?.icon) { - append(this.container, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); - } - if (this.options?.label) { - append(this.container, $('span.pre-releaselabel', undefined, localize('pre-release-label', "Pre-Release"))); - } + append(this.container, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); } } @@ -593,8 +587,7 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (extension.local?.isPreReleaseVersion || extension.gallery?.properties.isPreReleaseVersion) { return undefined; } - const preReleaseVersionLink = `[${localize('Show prerelease version', "Pre-Release version")}](${URI.parse(`command:workbench.extensions.action.showPreReleaseVersion?${encodeURIComponent(JSON.stringify([extension.identifier.id]))}`)})`; - return localize('has prerelease', "This extension has a {0} available", preReleaseVersionLink); + return localize('has prerelease', "This extension has a Pre-Release version available"); } } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index d4d41664b832c..9da142019966a 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -88,8 +88,7 @@ margin-left: 10px; } -.extension-editor > .header > .details > .title > .pre-release:not(:empty) { - display: flex; +.extension-editor > .header > .details > .title > .pre-release { font-size: 10px; margin-left: 10px; padding: 0px 4px; From 365cda0acd2fb3f0ffd24f9e9967d4a724165d0c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 23:18:09 +0100 Subject: [PATCH 0248/2210] Fix #138130 --- .../extensions/browser/extensionEditor.ts | 27 ++++++++++++------- .../extensions/browser/extensionsActions.ts | 6 +++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index c7652013e1515..d836e37b596b0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -579,7 +579,7 @@ export class ExtensionEditor extends EditorPane { let preReleaseText: string | undefined; reset(template.preReleaseText); const disposables = this.transientDisposables.add(new DisposableStore()); - const updatePreReleaseText = () => { + const updatePreReleaseText = (layout: boolean) => { const newPreReleaseText = ExtensionHoverWidget.getPreReleaseMessage(extension); if (preReleaseText !== newPreReleaseText) { preReleaseText = newPreReleaseText; @@ -589,12 +589,15 @@ export class ExtensionEditor extends EditorPane { append(template.preReleaseText, $(`span${ThemeIcon.asCSSSelector(preReleaseIcon)}`)); disposables.add(this.renderMarkdownText(preReleaseText, template.preReleaseText)); } + if (layout && this.dimension) { + this.layout(this.dimension); + } } }; - updatePreReleaseText(); + updatePreReleaseText(false); this.transientDisposables.add(this.extensionsWorkbenchService.onChange(e => { if (e && areSameExtensions(e.identifier, extension.identifier)) { - updatePreReleaseText(); + updatePreReleaseText(true); } })); } @@ -602,7 +605,7 @@ export class ExtensionEditor extends EditorPane { private setStatus(extension: IExtension, extensionStatus: ExtensionStatusAction, template: IExtensionEditorTemplate): void { const disposables = new DisposableStore(); this.transientDisposables.add(disposables); - const updateStatus = () => { + const updateStatus = (layout: boolean) => { disposables.clear(); reset(template.status); const status = extensionStatus.status; @@ -613,9 +616,12 @@ export class ExtensionEditor extends EditorPane { } disposables.add(this.renderMarkdownText(status.message.value, append(template.status, $('.status-text')))); } + if (layout && this.dimension) { + this.layout(this.dimension); + } }; - updateStatus(); - this.transientDisposables.add(extensionStatus.onDidChangeStatus(() => updateStatus())); + updateStatus(false); + this.transientDisposables.add(extensionStatus.onDidChangeStatus(() => updateStatus(true))); const updateActionLayout = () => template.actionsAndStatusContainer.classList.toggle('list-layout', extension.state === ExtensionState.Installed); updateActionLayout(); @@ -623,7 +629,7 @@ export class ExtensionEditor extends EditorPane { } private setRecommendationText(extension: IExtension, template: IExtensionEditorTemplate): void { - const updateRecommendationText = () => { + const updateRecommendationText = (layout: boolean) => { reset(template.recommendation); const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { @@ -635,9 +641,12 @@ export class ExtensionEditor extends EditorPane { } else if (this.extensionIgnoredRecommendationsService.globalIgnoredRecommendations.indexOf(extension.identifier.id.toLowerCase()) !== -1) { append(template.recommendation, $(`div.recommendation-text`, undefined, localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."))); } + if (layout && this.dimension) { + this.layout(this.dimension); + } }; - updateRecommendationText(); - this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationText())); + updateRecommendationText(false); + this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationText(true))); } private renderMarkdownText(markdownText: string, parent: HTMLElement): IDisposable { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 9c33b9880402b..21cdb0c044428 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2372,6 +2372,12 @@ export class ExtensionStatusAction extends ExtensionAction { } private updateStatus(status: ExtensionStatus | undefined, updateClass: boolean): void { + if (this._status === status) { + return; + } + if (this._status && status && this._status.message === status.message && this._status.icon?.id === status.icon?.id) { + return; + } this._status = status; if (updateClass) { if (this._status?.icon === errorIcon) { From 785e46c6cf8b733bfa407eef45d054c7e838c99e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Dec 2021 23:24:47 +0100 Subject: [PATCH 0249/2210] #138128 remove bookmark when not needed --- .../contrib/extensions/browser/extensionsWidgets.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index f80a7ed0efa0c..4bdb4b4592a59 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -254,11 +254,15 @@ export class PreReleaseBookmarkWidget extends ExtensionWidget { if (this.extension.isBuiltin) { return; } - if (this.extension.hasPreReleaseVersion) { - this.element = append(this.parent, $('div.extension-bookmark')); - const preRelease = append(this.element, $('.pre-release')); - append(preRelease, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); + if (!this.extension.hasPreReleaseVersion) { + return; + } + if (this.extension.state === ExtensionState.Installed && this.extension.local?.isPreReleaseVersion) { + return; } + this.element = append(this.parent, $('div.extension-bookmark')); + const preRelease = append(this.element, $('.pre-release')); + append(preRelease, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon))); } } From e53bee0fbfb174c7ca47914bdae63fbced61183f Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 1 Dec 2021 23:34:36 +0100 Subject: [PATCH 0250/2210] Increase timeouts (since now we run remote integration tests too) --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 312852c4eeb56..f4a8bd601dcd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: windows: name: Windows runs-on: windows-latest - timeout-minutes: 30 + timeout-minutes: 40 env: CHILD_CONCURRENCY: "1" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -95,7 +95,7 @@ jobs: linux: name: Linux runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 40 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: @@ -177,7 +177,7 @@ jobs: darwin: name: macOS runs-on: macos-latest - timeout-minutes: 30 + timeout-minutes: 40 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: @@ -251,7 +251,7 @@ jobs: hygiene: name: Hygiene, Layering and Monaco Editor runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 40 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: From 0519295e3fc3a3b76a48279a8cfc42dabcf893e4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Dec 2021 00:01:39 +0100 Subject: [PATCH 0251/2210] Fix #138271 --- .../common/extensionManagement.ts | 4 +++- .../node/extensionManagementService.ts | 17 ++++++++++++----- .../browser/extensionsWorkbenchService.ts | 18 +++++++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 2f63c0971df81..309613f6554e0 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -268,8 +268,11 @@ export interface IGalleryMetadata { id: string; publisherId: string; publisherDisplayName: string; + isPreReleaseVersion: boolean, } +export type Metadata = Partial; + export interface ILocalExtension extends IExtension { isMachineScoped: boolean; publisherId: string | null; @@ -389,7 +392,6 @@ export class ExtensionManagementError extends Error { } } -export type Metadata = Partial; export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean, installPreReleaseVersion?: boolean }; export type InstallVSIXOptions = Omit & { installOnlyNewlyAddedFromExtensionPack?: boolean }; export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean }; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 0bb7d16806a50..000d3a81661c7 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -148,7 +148,11 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); - local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((local.manifest).__metadata || {}), ...metadata }); + const localMetadata: Metadata = { ...((local.manifest).__metadata || {}), ...metadata }; + if (metadata.isPreReleaseVersion) { + localMetadata.preRelease = true; + } + local = await this.extensionsScanner.saveMetadataForLocalExtension(local, localMetadata); this.manifestCache.invalidate(); return local; } @@ -354,7 +358,7 @@ class InstallVSIXTask extends AbstractInstallExtensionTask { const identifierWithVersion = new ExtensionIdentifierWithVersion(this.identifier, this.manifest.version); const installedExtensions = await this.extensionsScanner.scanExtensions(ExtensionType.User); const existing = installedExtensions.find(i => areSameExtensions(this.identifier, i.identifier)); - const metadata = await this.getMetadata(this.identifier.id, token); + const metadata = await this.getMetadata(this.identifier.id, this.manifest.version, token); metadata.isMachineScoped = this.options.isMachineScoped || existing?.isMachineScoped; metadata.isBuiltin = this.options.isBuiltin || existing?.isBuiltin; @@ -385,11 +389,14 @@ class InstallVSIXTask extends AbstractInstallExtensionTask { return this.installExtension({ zipPath: path.resolve(this.location.fsPath), identifierWithVersion, metadata }, token); } - private async getMetadata(name: string, token: CancellationToken): Promise { + private async getMetadata(id: string, version: string, token: CancellationToken): Promise { try { - const galleryExtension = (await this.galleryService.query({ names: [name], pageSize: 1 }, token)).firstPage[0]; + let [galleryExtension] = await this.galleryService.getExtensions([{ id, version }], token); + if (!galleryExtension) { + [galleryExtension] = await this.galleryService.getExtensions([{ id }], token); + } if (galleryExtension) { - return { id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId }; + return { id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId, isPreReleaseVersion: galleryExtension.properties.isPreReleaseVersion, preRelease: galleryExtension.properties.isPreReleaseVersion }; } } catch (error) { /* Ignore Error */ diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 2cd879d72aac3..db58fb870c72b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -437,14 +437,26 @@ class Extensions extends Disposable { hasChanged = true; } - const compatible = await this.getCompatibleExtension(gallery, !!extension.local?.isPreReleaseVersion); + const compatible = await this.getCompatibleExtension(gallery, extension.local.isPreReleaseVersion); if (compatible) { - const local = extension.local.identifier.uuid ? extension.local : await this.server.extensionManagementService.updateMetadata(extension.local, { id: compatible.identifier.uuid, publisherDisplayName: compatible.publisherDisplayName, publisherId: compatible.publisherId }); - extension.local = local; extension.gallery = compatible; hasChanged = true; } + if (!extension.local.identifier.uuid) { + let galleryExtension = !extension.outdated ? extension.gallery : undefined; + if (!galleryExtension) { + [galleryExtension] = await this.galleryService.getExtensions([{ ...extension.local.identifier, version: extension.version }], CancellationToken.None); + } + if (!galleryExtension) { + [galleryExtension] = await this.galleryService.getExtensions([extension.local.identifier], CancellationToken.None); + } + if (galleryExtension) { + const local = await this.server.extensionManagementService.updateMetadata(extension.local, { id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId, isPreReleaseVersion: gallery.properties.isPreReleaseVersion }); + extension.local = local; + } + } + const unsupportedPreRelease = extensionsControlManifest.unsupportedPreReleaseExtensions ? extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()] : undefined; if (unsupportedPreRelease) { if (isBoolean(extension.isUnsupported) || !areSameExtensions({ id: extension.isUnsupported.preReleaseExtension.id }, { id: unsupportedPreRelease.id })) { From f9fad166caa3388fb8be7ab8fb0359e58aa8cd30 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Dec 2021 00:09:01 +0100 Subject: [PATCH 0252/2210] #138271 fix tests --- .../extensions/test/electron-browser/extensionsActions.test.ts | 2 ++ .../test/electron-browser/extensionsWorkbenchService.test.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 7cb8d4de8e130..4d033704aac83 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -390,6 +390,7 @@ suite('ExtensionsActions', () => { const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', gallery); + instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', [gallery]); assert.ok(!testObject.enabled); return new Promise(c => { testObject.onDidChange(() => { @@ -413,6 +414,7 @@ suite('ExtensionsActions', () => { const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', gallery); + instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', [gallery]); await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); const promise = Event.toPromise(testObject.onDidChange); installEvent.fire({ identifier: local.identifier, source: gallery }); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 4b4320e05fc5e..e950ee617d31e 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -308,6 +308,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local1, local2]); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery1)); instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', gallery1); + instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', [gallery1]); testObject = await aWorkbenchService(); await testObject.queryLocal(); From 2c90237001a93327971b05cbcbde27b8ff151412 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Dec 2021 00:19:11 +0100 Subject: [PATCH 0253/2210] Fix #138234 --- .../extensions/browser/extensionsActions.ts | 37 +------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 21cdb0c044428..58e3462d3ff02 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -65,40 +65,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ViewContainerLocation } from 'vs/workbench/common/views'; import { flatten } from 'vs/base/common/arrays'; import { isBoolean } from 'vs/base/common/types'; - -function getRelativeDateLabel(date: Date): string { - const delta = new Date().getTime() - date.getTime(); - - const year = 365 * 24 * 60 * 60 * 1000; - if (delta > year) { - const noOfYears = Math.floor(delta / year); - return noOfYears > 1 ? localize('noOfYearsAgo', "{0} years ago", noOfYears) : localize('one year ago', "1 year ago"); - } - - const month = 30 * 24 * 60 * 60 * 1000; - if (delta > month) { - const noOfMonths = Math.floor(delta / month); - return noOfMonths > 1 ? localize('noOfMonthsAgo', "{0} months ago", noOfMonths) : localize('one month ago', "1 month ago"); - } - - const day = 24 * 60 * 60 * 1000; - if (delta > day) { - const noOfDays = Math.floor(delta / day); - return noOfDays > 1 ? localize('noOfDaysAgo', "{0} days ago", noOfDays) : localize('one day ago', "1 day ago"); - } - - const hour = 60 * 60 * 1000; - if (delta > hour) { - const noOfHours = Math.floor(delta / day); - return noOfHours > 1 ? localize('noOfHoursAgo', "{0} hours ago", noOfHours) : localize('one hour ago', "1 hour ago"); - } - - if (delta > 0) { - return localize('just now', "Just now"); - } - - return ''; -} +import { fromNow } from 'vs/base/common/date'; export class PromptExtensionInstallFailureAction extends Action { @@ -1175,7 +1142,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { return { id: v.version, label: v.version, - description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension!.version ? ` (${localize('current', "current")})` : ''}`, + description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension!.version ? ` (${localize('current', "current")})` : ''}`, latest: i === 0, ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`, isPreReleaseVersion: v.isPreReleaseVersion From 72860a0457782ba97dd28c1fa65376d486dba6e3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 2 Dec 2021 00:23:57 +0100 Subject: [PATCH 0254/2210] Improve positioning of native IME popup --- .../editor/browser/controller/textAreaHandler.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 36dec6dfd97de..7abc24eed9410 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -592,6 +592,8 @@ export class TextAreaHandler extends ViewPart { // In case the textarea contains a word, we're going to try to align the textarea's cursor // with our cursor by scrolling the textarea as much as possible this.textArea.domNode.scrollLeft = this._primaryCursorVisibleRange.left; + const lineCount = this._newlinecount(this.textArea.domNode.value.substr(0, this.textArea.domNode.selectionStart)); + this.textArea.domNode.scrollTop = lineCount * this._lineHeight; return; } @@ -602,6 +604,19 @@ export class TextAreaHandler extends ViewPart { ); } + private _newlinecount(text: string): number { + let result = 0; + let startIndex = -1; + do { + startIndex = text.indexOf('\n', startIndex + 1); + if (startIndex === -1) { + break; + } + result++; + } while (true); + return result; + } + private _renderInsideEditor(renderedPosition: Position | null, top: number, left: number, width: number, height: number): void { this._lastRenderPosition = renderedPosition; const ta = this.textArea; From e8775192044daaadbfe624388bec1145cc59ee8f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Dec 2021 00:42:11 +0100 Subject: [PATCH 0255/2210] Fix #138140 --- .../preferences/browser/preferencesEditor.ts | 16 +++++++++------- .../preferences/browser/preferencesRenderers.ts | 3 --- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 57ff47b27db18..d98c4b94cd7b5 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -15,7 +15,8 @@ import { SettingsEditorModel } from 'vs/workbench/services/preferences/common/pr export class SettingsEditorContribution extends Disposable { static readonly ID: string = 'editor.contrib.settings'; - private _currentRenderer: IPreferencesRenderer | undefined; + private currentRenderer: IPreferencesRenderer | undefined; + private readonly disposables = this._register(new DisposableStore()); constructor( private readonly editor: ICodeEditor, @@ -30,24 +31,25 @@ export class SettingsEditorContribution extends Disposable { } private async _createPreferencesRenderer(): Promise { - this._currentRenderer?.dispose(); - this._currentRenderer = undefined; + this.disposables.clear(); + this.currentRenderer = undefined; const model = this.editor.getModel(); if (model) { const settingsModel = await this.preferencesService.createPreferencesEditorModel(model.uri); if (settingsModel instanceof SettingsEditorModel && this.editor.getModel()) { + this.disposables.add(settingsModel); switch (settingsModel.configurationTarget) { case ConfigurationTarget.WORKSPACE: - this._currentRenderer = this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel); + this.currentRenderer = this.disposables.add(this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel)); break; default: - this._currentRenderer = this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel); + this.currentRenderer = this.disposables.add(this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel)); break; } } - this._currentRenderer?.render(); + this.currentRenderer?.render(); } } } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 175a7b6974c5e..37a22da335b10 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -134,9 +134,6 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend return !!(editableSetting && this.editSettingActionRenderer.activateOnSetting(editableSetting)); } - public override dispose(): void { - this.preferencesModel.dispose(); - } } export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer { From fc86d42b9a8e10fa8044f544d013a2db98348966 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 1 Dec 2021 17:08:11 -0800 Subject: [PATCH 0256/2210] log layout --- src/vs/workbench/browser/layout.ts | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a249fb65d7862..083b2f8dac68f 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -50,6 +50,7 @@ import { Schemas } from 'vs/base/common/network'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { AuxiliaryBarPart, AUXILIARYBAR_ENABLED } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; type PanelAlignment = 'left' | 'center' | 'right' | 'justify'; @@ -183,6 +184,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private themeService!: IThemeService; private statusBarService!: IStatusbarService; private logService!: ILogService; + private telemetryService!: ITelemetryService; protected readonly state = { fullscreen: false, @@ -268,6 +270,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.themeService = accessor.get(IThemeService); this.extensionService = accessor.get(IExtensionService); this.logService = accessor.get(ILogService); + this.telemetryService = accessor.get(ITelemetryService); // Parts this.editorService = accessor.get(IEditorService); @@ -2166,6 +2169,38 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi height }; + type StartupLayoutEvent = { + activityBarVisible: boolean; + sideBarVisible: boolean; + auxiliaryBarVisible: boolean; + panelVisible: boolean; + statusbarVisible: boolean; + sideBarPosition: string; + panelPosition: string; + }; + + type StartupLayoutEventClassification = { + activityBarVisible: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + sideBarVisible: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + auxiliaryBarVisible: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + panelVisible: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + statusbarVisible: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + sideBarPosition: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + panelPosition: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + }; + + const layoutDescriptor: StartupLayoutEvent = { + activityBarVisible: !this.state.auxiliaryBar.hidden, + sideBarVisible: !this.state.sideBar.hidden, + auxiliaryBarVisible: !this.state.auxiliaryBar.hidden, + panelVisible: !this.state.panel.hidden, + statusbarVisible: !this.state.statusBar.hidden, + sideBarPosition: positionToString(this.state.sideBar.position), + panelPosition: positionToString(this.state.panel.position), + }; + + this.telemetryService.publicLog2('startupLayout', layoutDescriptor); + return result; } From 9d674adf0aec7616a3ee6d6864144fd9af493141 Mon Sep 17 00:00:00 2001 From: John Murray Date: Thu, 2 Dec 2021 01:09:37 +0000 Subject: [PATCH 0257/2210] Improve Explorer welcome view (fix #137414) (#137475) * Improve Explorer welcome view (fix #137414) * don't duplicate noFolder welcome help in the IsWebContext case * simplify noFolderHelp when-clauses --- .../workbench/contrib/files/browser/explorerViewlet.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 68414e0a55105..e126d1b0159ba 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -298,10 +298,18 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { order: 1 }); +viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize({ key: 'noFolderButEditorsHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "You have not yet opened a folder.\n[Open Folder](command:{0})\nOpening a folder will close all currently open editors. To keep them open, [add a folder](command:{1}) instead.", commandId, AddRootFolderAction.ID), + when: ContextKeyExpr.and(ContextKeyExpr.has('editorIsOpen'), ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext))), + group: ViewContentGroups.Open, + order: 1 +}); + viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), - when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)), + when: ContextKeyExpr.and(ContextKeyExpr.has('editorIsOpen')?.negate(), ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext))), group: ViewContentGroups.Open, order: 1 }); From 68a1e2f872f8869b004eec284917782474730e0b Mon Sep 17 00:00:00 2001 From: tanhakabir Date: Wed, 1 Dec 2021 19:46:50 -0800 Subject: [PATCH 0258/2210] Update team list in endgame notebook --- .vscode/notebooks/my-endgame.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index ffe2a29d04ad0..3d0c3481c879e 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -157,7 +157,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed sort:updated-asc label:bug -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:btholt -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:dynamicwebpaige -author:eamodio -author:egamma -author:fiveisprime -author:greazer -author:gregvanl -author:hediet -author:IanMatthewHuff -author:isidorn -author:ItalyPaleAle -author:JacksonKearl -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr-author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:ornellaalt -author:orta -author:rchiodo -author:rebornix -author:RMacfarlane -author:roblourens -author:rzhao271 -author:sana-ajani -author:sandy081 -author:sbatten -author:stevencl -author:TylerLeonhardt -author:Tyriar -author:weinand " + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed sort:updated-asc label:bug -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:dynamicwebpaige -author:eamodio -author:egamma -author:fiveisprime -author:greazer -author:gregvanl -author:hediet -author:IanMatthewHuff -author:isidorn -author:ItalyPaleAle -author:JacksonKearl -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr-author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:ornellaalt -author:orta -author:rchiodo -author:rebornix -author:roblourens -author:rzhao271 -author:sana-ajani -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand " }, { "kind": 1, From 9d427e4df4428099f568b6dd4300808a271a0a77 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Dec 2021 07:34:13 +0100 Subject: [PATCH 0259/2210] smoke test tweaks - remove timeout on shutdown - log exactly where time is spend on shutdown (web) - stop spawning code when terminated --- test/automation/src/code.ts | 8 ++++++++ test/automation/src/playwrightDriver.ts | 16 +++++++++++++--- test/smoke/src/main.ts | 3 --- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 95471907abff4..3d1743e0f28cb 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -29,7 +29,15 @@ export interface SpawnOptions { browser?: 'chromium' | 'webkit' | 'firefox'; } +let stopped = false; +process.on('exit', () => stopped = true); +process.on('SIGINT', () => stopped = true); +process.on('SIGTERM', () => stopped = true); + export async function spawn(options: SpawnOptions): Promise { + if (stopped) { + throw new Error('Smoke test process has terminated, refusing to spawn Code'); + } await copyExtension(repoPath, options.extensionsPath, 'vscode-notebook-tests'); diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index eb68ebea29677..a7cc9ee74e347 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -61,22 +61,32 @@ class PlaywrightDriver implements IDriver { async exitApplication() { try { - await this.context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) }); + await this.warnAfter(this.context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) }), 5000, 'Stopping playwright trace took >5seconds'); } catch (error) { console.warn(`Failed to stop playwright tracing: ${error}`); } try { - await this.browser.close(); + await this.warnAfter(this.browser.close(), 5000, 'Closing playwright browser took >5seconds'); } catch (error) { console.warn(`Failed to close browser: ${error}`); } - await teardown(this.server); + await this.warnAfter(teardown(this.server), 5000, 'Tearing down server took >5seconds'); return false; } + private async warnAfter(promise: Promise, delay: number, msg: string): Promise { + const timeout = setTimeout(() => console.warn(msg), delay); + + try { + await promise; + } finally { + clearTimeout(timeout); + } + } + async dispatchKeybinding(windowId: number, keybinding: string) { const chords = keybinding.split(' '); for (let i = 0; i < chords.length; i++) { diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index f526eb19bf49a..8f23a483e15fa 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import { timeout } from './utils'; import { promisify } from 'util'; import { gracefulify } from 'graceful-fs'; import * as cp from 'child_process'; @@ -336,8 +335,6 @@ before(async function () { }); after(async function () { - await timeout(500); // wait for shutdown - if (opts.log) { const logsDir = path.join(userDataDir, 'logs'); const destLogsDir = path.join(path.dirname(opts.log), 'logs'); From ff970e88e5240b8a3bb28fe5ce105ef44ffcd4ca Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Dec 2021 07:38:04 +0100 Subject: [PATCH 0260/2210] Revert "smoke - :up: retry timeout" This reverts commit d60b844aa3192971757eb28dd8cef5a6a4ee063b. --- test/automation/src/code.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 3d1743e0f28cb..45389defd0e2e 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -66,7 +66,7 @@ async function poll( fn: () => Thenable, acceptFn: (result: T) => boolean, timeoutMessage: string, - retryCount: number = 400, + retryCount: number = 200, retryInterval: number = 100 // millis ): Promise { let trial = 1; From cbab504ea938e21ccf388e4b5f6a7df19600d3df Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Dec 2021 09:23:33 +0100 Subject: [PATCH 0261/2210] Update editor configuration only after extensions are registered (#138302) * delay updating editor configurations when installe extensions are registered * async function over Promise.then Co-authored-by: Benjamin Pasero --- .../browser/parts/editor/editorConfiguration.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorConfiguration.ts b/src/vs/workbench/browser/parts/editor/editorConfiguration.ts index e452fbb1a5463..5812b5340e361 100644 --- a/src/vs/workbench/browser/parts/editor/editorConfiguration.ts +++ b/src/vs/workbench/browser/parts/editor/editorConfiguration.ts @@ -11,6 +11,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigu import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { IEditorResolverService, RegisteredEditorInfo, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export class DynamicEditorGroupAutoLockConfiguration extends Disposable implements IWorkbenchContribution { @@ -32,12 +33,21 @@ export class DynamicEditorGroupAutoLockConfiguration extends Disposable implemen private configurationNode: IConfigurationNode | undefined; constructor( - @IEditorResolverService private readonly editorResolverService: IEditorResolverService + @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @IExtensionService extensionService: IExtensionService, ) { super(); - this.updateConfiguration(); - this.registerListeners(); + // Editor configurations are getting updated very aggressively + // (atleast 20 times) while the extensions are getting registered. + // As such push out the dynamic editor auto lock configuration + // until after extensions registered. + (async ()=> { + await extensionService.whenInstalledExtensionsRegistered(); + + this.updateConfiguration(); + this.registerListeners(); + })(); } private registerListeners(): void { From 60ea8cb8ba1519d8496b4b327d06519cb4ab8664 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Dec 2021 09:37:34 +0100 Subject: [PATCH 0262/2210] Fix #138262 --- .../extensions/browser/extensionEditor.ts | 35 ++----------------- .../extensions/browser/extensionsWidgets.ts | 3 +- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index d836e37b596b0..56f8981d53883 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -23,7 +23,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, IExtension, ExtensionContainers, ExtensionEditorTab, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions'; -import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, ExtensionHoverWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; +import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { @@ -68,7 +68,7 @@ import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { attachKeybindingLabelStyler } from 'vs/platform/theme/common/styler'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { errorIcon, infoIcon, preReleaseIcon, starEmptyIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { errorIcon, infoIcon, starEmptyIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; @@ -151,7 +151,6 @@ interface IExtensionEditorTemplate { actionsAndStatusContainer: HTMLElement; extensionActionBar: ActionBar; status: HTMLElement; - preReleaseText: HTMLElement; recommendation: HTMLElement; navbar: NavBar; content: HTMLElement; @@ -303,7 +302,6 @@ export class ExtensionEditor extends EditorPane { })); const status = append(actionsAndStatusContainer, $('.status')); - const preReleaseText = append(details, $('.pre-release-text')); const recommendation = append(details, $('.recommendation')); this._register(Event.chain(extensionActionBar.onDidRun) @@ -336,7 +334,6 @@ export class ExtensionEditor extends EditorPane { rating, actionsAndStatusContainer, extensionActionBar, - preReleaseText, status, recommendation }; @@ -526,7 +523,6 @@ export class ExtensionEditor extends EditorPane { this.transientDisposables.add(disposable); } - this.setPreReleaseText(extension, template); this.setStatus(extension, extensionStatus, template); this.setRecommendationText(extension, template); @@ -575,33 +571,6 @@ export class ExtensionEditor extends EditorPane { this.editorLoadComplete = true; } - private setPreReleaseText(extension: IExtension, template: IExtensionEditorTemplate): void { - let preReleaseText: string | undefined; - reset(template.preReleaseText); - const disposables = this.transientDisposables.add(new DisposableStore()); - const updatePreReleaseText = (layout: boolean) => { - const newPreReleaseText = ExtensionHoverWidget.getPreReleaseMessage(extension); - if (preReleaseText !== newPreReleaseText) { - preReleaseText = newPreReleaseText; - disposables.clear(); - reset(template.preReleaseText); - if (preReleaseText) { - append(template.preReleaseText, $(`span${ThemeIcon.asCSSSelector(preReleaseIcon)}`)); - disposables.add(this.renderMarkdownText(preReleaseText, template.preReleaseText)); - } - if (layout && this.dimension) { - this.layout(this.dimension); - } - } - }; - updatePreReleaseText(false); - this.transientDisposables.add(this.extensionsWorkbenchService.onChange(e => { - if (e && areSameExtensions(e.identifier, extension.identifier)) { - updatePreReleaseText(true); - } - })); - } - private setStatus(extension: IExtension, extensionStatus: ExtensionStatusAction, template: IExtensionEditorTemplate): void { const disposables = new DisposableStore(); this.transientDisposables.add(disposables); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 4bdb4b4592a59..1501d55fa7c09 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -591,7 +591,8 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (extension.local?.isPreReleaseVersion || extension.gallery?.properties.isPreReleaseVersion) { return undefined; } - return localize('has prerelease', "This extension has a Pre-Release version available"); + const preReleaseVersionLink = `[${localize('Show prerelease version', "Pre-Release version")}](${URI.parse(`command:workbench.extensions.action.showPreReleaseVersion?${encodeURIComponent(JSON.stringify([extension.identifier.id]))}`)})`; + return localize('has prerelease', "This extension has a {0} available", preReleaseVersionLink); } } From 8b53cb61a7a6260bd5a1272f373bd8a3f1445891 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Dec 2021 09:42:26 +0100 Subject: [PATCH 0263/2210] remove recommendation text for installed extensions --- .../workbench/contrib/extensions/browser/extensionEditor.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 56f8981d53883..21ba089444663 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -599,7 +599,6 @@ export class ExtensionEditor extends EditorPane { private setRecommendationText(extension: IExtension, template: IExtensionEditorTemplate): void { const updateRecommendationText = (layout: boolean) => { - reset(template.recommendation); const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { const reasonText = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; @@ -614,6 +613,10 @@ export class ExtensionEditor extends EditorPane { this.layout(this.dimension); } }; + reset(template.recommendation); + if (extension.state === ExtensionState.Installed) { + return; + } updateRecommendationText(false); this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationText(true))); } From a59f59b482e8a034d19f6be024b82cdd6d675394 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Dec 2021 10:39:21 +0100 Subject: [PATCH 0264/2210] debt - do not reject without reason --- src/vs/workbench/contrib/files/browser/fileImportExport.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts index 9a1fd5046b312..8ae4751f6e40b 100644 --- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts +++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts @@ -31,6 +31,7 @@ import { listenStream } from 'vs/base/common/stream'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { once } from 'vs/base/common/functional'; import { coalesce } from 'vs/base/common/arrays'; +import { canceled } from 'vs/base/common/errors'; //#region Browser File Upload (drag and drop, input element) @@ -686,7 +687,7 @@ export class FileDownload { disposables.add(once(token.onCancellationRequested)(() => { disposables.dispose(); - reject(); + reject(canceled()); })); listenStream(sourceStream, { From d29de45bdb612bfaece8e9b685ad5429925891cd Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Thu, 2 Dec 2021 11:37:35 +0100 Subject: [PATCH 0265/2210] use final DAP 1.51.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f46c6eaa2faa7..e16f53ff1ef9c 100644 --- a/package.json +++ b/package.json @@ -203,7 +203,7 @@ "util": "^0.12.4", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", - "vscode-debugprotocol": "1.50.0", + "vscode-debugprotocol": "1.51.0", "vscode-nls-dev": "^3.3.1", "vscode-telemetry-extractor": "^1.9.5", "webpack": "^5.42.0", diff --git a/yarn.lock b/yarn.lock index 83066307eed1b..5bcbdcd1f10de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10387,10 +10387,10 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vscode-debugprotocol@1.50.0: - version "1.50.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.50.0.tgz#6f3e1204d3f4439afb4a5dc9d5396ecef0a94245" - integrity sha512-kPC8LC0rwkKMjWnbDmffoNKKhoNt40ZaeJGXFBAT/KmBX38M71EG5J5uosaqvHTIWkucKXAw1azh8doydxwyZw== +vscode-debugprotocol@1.51.0: + version "1.51.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.51.0.tgz#c03168dac778b6c24ce17b3511cb61e89c11b2df" + integrity sha512-dzKWTMMyebIMPF1VYMuuQj7gGFq7guR8AFya0mKacu+ayptJfaRuM0mdHCqiOth4FnRP8mPhEroFPx6Ift8wHA== vscode-nls-dev@^3.3.1: version "3.3.1" From af8b6ad9f060e9e6bfe12fa02ac4d61644a30d88 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 2 Dec 2021 11:40:08 +0100 Subject: [PATCH 0266/2210] SCM - Introduce another open changes command got editor/command palette (#138293) * Introduce another command got editor/command palette --- extensions/git/package.json | 12 +++++++++++- extensions/git/src/commands.ts | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index b9284c78b8c7e..cd0b40e2e7dff 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -84,6 +84,12 @@ "command": "git.openChange", "title": "%command.openChange%", "category": "Git", + "icon": "$(compare-changes)" + }, + { + "command": "git.openChangeEditor", + "title": "%command.openChange%", + "category": "Git", "icon": "$(compare-changes)", "enablement": "scmActiveResourceHasChanges" }, @@ -602,6 +608,10 @@ }, { "command": "git.openChange", + "when": "false" + }, + { + "command": "git.openChangeEditor", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { @@ -1359,7 +1369,7 @@ "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInNotebookTextDiffEditor && resourceScheme =~ /^git$|^file$/" }, { - "command": "git.openChange", + "command": "git.openChangeEditor", "group": "navigation", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file" }, diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index b37d83ab4a46f..deef8a26554d4 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -764,6 +764,7 @@ export class CommandCenter { } @command('git.openChange') + @command('git.openChangeEditor') async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let resources: Resource[] | undefined = undefined; From 1af9ac1b8f7882dc5690edd67a34b5bb47c8ca58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 2 Dec 2021 11:51:09 +0100 Subject: [PATCH 0267/2210] fixes #138274 --- extensions/git/src/test/smoke.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/git/src/test/smoke.test.ts b/extensions/git/src/test/smoke.test.ts index 9005bb4956325..ed654eae7fb43 100644 --- a/extensions/git/src/test/smoke.test.ts +++ b/extensions/git/src/test/smoke.test.ts @@ -42,13 +42,12 @@ suite('git smoke test', function () { suiteSetup(async function () { fs.writeFileSync(file('app.js'), 'hello', 'utf8'); fs.writeFileSync(file('index.pug'), 'hello', 'utf8'); - cp.execSync('git init', { cwd }); + cp.execSync('git init -b main', { cwd }); cp.execSync('git config user.name testuser', { cwd }); cp.execSync('git config user.email monacotools@microsoft.com', { cwd }); cp.execSync('git config commit.gpgsign false', { cwd }); cp.execSync('git add .', { cwd }); cp.execSync('git commit -m "initial commit"', { cwd }); - cp.execSync('git branch -m main', { cwd }); // make sure git is activated const ext = extensions.getExtension('vscode.git'); From 7fd177d4941e44e64dba2a95a2ee9c632ebc6a5c Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 2 Dec 2021 13:42:54 +0100 Subject: [PATCH 0268/2210] Remove array from autoamtion profile schema Fixes #138290 --- .../common/terminalPlatformConfiguration.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts index 41700242b2474..ca3e4d5730070 100644 --- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts +++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts @@ -70,6 +70,21 @@ const terminalProfileSchema: IJSONSchema = { } }; +const terminalAutomationProfileSchema: IJSONSchema = { + type: 'object', + required: ['path'], + properties: { + path: { + description: localize('terminalAutomationProfile.path', 'A single path to a shell executable.'), + type: ['string'], + items: { + type: 'string' + } + }, + ...terminalProfileBaseProperties + } +}; + const shellDeprecationMessageLinux = localize('terminal.integrated.shell.linux.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.linux#`', '`#terminal.integrated.defaultProfile.linux#`'); const shellDeprecationMessageOsx = localize('terminal.integrated.shell.osx.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.osx#`', '`#terminal.integrated.defaultProfile.osx#`'); const shellDeprecationMessageWindows = localize('terminal.integrated.shell.windows.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.windows#`', '`#terminal.integrated.defaultProfile.windows#`'); @@ -120,7 +135,7 @@ const terminalPlatformConfiguration: IConfigurationNode = { default: null, 'anyOf': [ { type: 'null' }, - terminalProfileSchema + terminalAutomationProfileSchema ], defaultSnippets: [ { @@ -138,7 +153,7 @@ const terminalPlatformConfiguration: IConfigurationNode = { default: null, 'anyOf': [ { type: 'null' }, - terminalProfileSchema + terminalAutomationProfileSchema ], defaultSnippets: [ { @@ -156,7 +171,7 @@ const terminalPlatformConfiguration: IConfigurationNode = { default: null, 'anyOf': [ { type: 'null' }, - terminalProfileSchema + terminalAutomationProfileSchema ], defaultSnippets: [ { From 78f57a43dc70cc40c92d320ccb654b9be6fe031d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Dec 2021 15:02:52 +0100 Subject: [PATCH 0269/2210] log prerelease query to telemetry --- .../common/extensionGalleryService.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index a4be6e4aa8aa2..6cea0d617fea2 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -198,6 +198,16 @@ type GalleryServiceQueryEvent = QueryTelemetryData & { readonly count?: string; }; +type GalleryServicePreReleaseQueryClassification = { + readonly duration: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', 'isMeasurement': true }; + readonly count: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + +type GalleryServicePreReleasesQueryEvent = { + readonly duration: number; + readonly count: number; +}; + class Query { constructor(private state = DefaultQueryState) { } @@ -687,12 +697,17 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } if (preReleaseVersions.size) { + const startTime = new Date().getTime(); const query = new Query() .withFlags(Flags.IncludeVersions, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, preReleaseVersions.size) .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') .withFilter(FilterType.ExtensionId, ...preReleaseVersions.keys()); const { galleryExtensions } = await this.queryGallery(query, targetPlatform, token); + this.telemetryService.publicLog2('galleryService:preReleasesQuery', { + duration: new Date().getTime() - startTime, + count: preReleaseVersions.size + }); if (galleryExtensions.length !== preReleaseVersions.size) { throw new Error('Not all extensions with latest versions are returned'); } From 93347ca210ab57f5e4534a40ce9fbdcbf44d94fa Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Thu, 2 Dec 2021 15:15:55 +0100 Subject: [PATCH 0270/2210] Move to latest LSIF indexer --- .github/workflows/rich-navigation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rich-navigation.yml b/.github/workflows/rich-navigation.yml index 0d386dc4c2fed..f5fd32493f111 100644 --- a/.github/workflows/rich-navigation.yml +++ b/.github/workflows/rich-navigation.yml @@ -36,6 +36,6 @@ jobs: with: languages: typescript repo-token: ${{ secrets.GITHUB_TOKEN }} - typescriptVersion: 0.6.0-next.19 + typescriptVersion: 0.6.0-next.21 configFiles: .lsifrc.json continue-on-error: true From e228f2a2b5da42911f4eb4656622948feab5732d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 2 Dec 2021 15:21:29 +0100 Subject: [PATCH 0271/2210] Add cancel to typescript promise rejection (#138333) Fixes #138269 --- .../src/typescriptServiceClient.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 3cf58c0ba8d81..9f484dd6f9dfe 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -1059,11 +1059,13 @@ function getDignosticsKind(event: Proto.Event) { } class ServerInitializingIndicator extends Disposable { - private _task?: { project: string | undefined, resolve: () => void, reject: () => void }; + private _task?: { project: string | undefined, resolve: () => void, reject: (error: Error) => void }; public reset(): void { if (this._task) { - this._task.reject(); + const error = new Error('Canceled'); + error.name = error.message; + this._task.reject(error); this._task = undefined; } } From 87be9f76206780eea25a44075ae0f6a5eb568d10 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 2 Dec 2021 15:26:57 +0100 Subject: [PATCH 0272/2210] align with other settings and rename `problems.compareOrder` to `problems.sortOrder`, https://github.com/microsoft/vscode/issues/135249 --- src/vs/editor/contrib/gotoError/markerNavigationService.ts | 2 +- .../workbench/contrib/markers/browser/markers.contribution.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/gotoError/markerNavigationService.ts b/src/vs/editor/contrib/gotoError/markerNavigationService.ts index d9213a66ce650..a8c27316ee488 100644 --- a/src/vs/editor/contrib/gotoError/markerNavigationService.ts +++ b/src/vs/editor/contrib/gotoError/markerNavigationService.ts @@ -47,7 +47,7 @@ export class MarkerList { this._resourceFilter = resourceFilter; } - const compareOrder = this._configService.getValue('problems.compareOrder'); + const compareOrder = this._configService.getValue('problems.sortOrder'); const compareMarker = (a: IMarker, b: IMarker): number => { let res = compare(a.resource.toString(), b.resource.toString()); if (compareOrder === 'position') { diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 073988a4dcf54..4649c94978cf4 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -101,7 +101,7 @@ Registry.as(Extensions.Configuration).registerConfigurat 'type': 'boolean', 'default': false }, - 'problems.compareOrder': { + 'problems.sortOrder': { 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_COMPARE_ORDER, 'type': 'string', 'default': 'severity', From 75455fd428a4839bc9e7e38b9936bb8060b81091 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 2 Dec 2021 15:43:40 +0100 Subject: [PATCH 0273/2210] update request-light. Fixes #136267 --- extensions/json-language-features/package.json | 2 +- extensions/json-language-features/server/package.json | 2 +- extensions/json-language-features/server/yarn.lock | 8 ++++---- extensions/json-language-features/yarn.lock | 8 ++++---- extensions/npm/package.json | 2 +- extensions/npm/yarn.lock | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 984d0d2e76aee..38e9fbc6e3247 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -136,7 +136,7 @@ ] }, "dependencies": { - "request-light": "^0.5.4", + "request-light": "^0.5.5", "vscode-extension-telemetry": "0.4.3", "vscode-languageclient": "^7.0.0", "vscode-nls": "^5.0.0" diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 988c2963e6b6c..2cbc5e4459cc6 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -13,7 +13,7 @@ "main": "./out/node/jsonServerMain", "dependencies": { "jsonc-parser": "^3.0.0", - "request-light": "^0.5.4", + "request-light": "^0.5.5", "vscode-json-languageservice": "^4.2.0-next.2", "vscode-languageserver": "^7.0.0", "vscode-uri": "^3.0.2" diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 68bffbb9371c8..2c9e39937d09e 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -17,10 +17,10 @@ jsonc-parser@^3.0.0: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== -request-light@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.4.tgz#497a98c6d8ae49536417a5e2d7f383b934f3e38c" - integrity sha512-t3566CMweOFlUk7Y1DJMu5OrtpoZEb6aSTsLQVT3wtrIEJ5NhcY9G/Oqxvjllzl4a15zXfFlcr9q40LbLVQJqw== +request-light@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.5.tgz#254ab0b38a1db2192170b599b05181934e14932b" + integrity sha512-AvjfJuhyT6dYfhtIBF+IpTPQco+Td1QJ6PsIJ5xui110vQ5p9HxHk+m1XJqXazLQT6CxxSx9eNv6R/+fu4bZig== vscode-json-languageservice@^4.2.0-next.2: version "4.2.0-next.2" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 2a4755d681653..b983a129bb9e4 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -39,10 +39,10 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -request-light@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.4.tgz#497a98c6d8ae49536417a5e2d7f383b934f3e38c" - integrity sha512-t3566CMweOFlUk7Y1DJMu5OrtpoZEb6aSTsLQVT3wtrIEJ5NhcY9G/Oqxvjllzl4a15zXfFlcr9q40LbLVQJqw== +request-light@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.5.tgz#254ab0b38a1db2192170b599b05181934e14932b" + integrity sha512-AvjfJuhyT6dYfhtIBF+IpTPQco+Td1QJ6PsIJ5xui110vQ5p9HxHk+m1XJqXazLQT6CxxSx9eNv6R/+fu4bZig== semver@^7.3.4: version "7.3.4" diff --git a/extensions/npm/package.json b/extensions/npm/package.json index e3baafd8bfe04..6aa1de094b8b3 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -21,7 +21,7 @@ "find-yarn-workspace-root": "^2.0.0", "jsonc-parser": "^2.2.1", "minimatch": "^3.0.4", - "request-light": "^0.5.4", + "request-light": "^0.5.5", "vscode-nls": "^5.0.0", "which": "^2.0.2", "which-pm": "^2.0.0" diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index 73160cfceafd7..fef65191b4baf 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -170,10 +170,10 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -request-light@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.4.tgz#497a98c6d8ae49536417a5e2d7f383b934f3e38c" - integrity sha512-t3566CMweOFlUk7Y1DJMu5OrtpoZEb6aSTsLQVT3wtrIEJ5NhcY9G/Oqxvjllzl4a15zXfFlcr9q40LbLVQJqw== +request-light@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.5.tgz#254ab0b38a1db2192170b599b05181934e14932b" + integrity sha512-AvjfJuhyT6dYfhtIBF+IpTPQco+Td1QJ6PsIJ5xui110vQ5p9HxHk+m1XJqXazLQT6CxxSx9eNv6R/+fu4bZig== sprintf-js@~1.0.2: version "1.0.3" From b395575f006838c268683a42292cce7656cc27ff Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 2 Dec 2021 09:55:53 -0500 Subject: [PATCH 0274/2210] Address comments about ? operator --- extensions/git/src/commands.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index deef8a26554d4..e72ce89e91a32 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -687,9 +687,9 @@ export class CommandCenter { const activeTextEditor = window.activeTextEditor; // Must extract these now because opening a new document will change the activeTextEditor reference - const previousVisibleRange = activeTextEditor && activeTextEditor.visibleRanges[0]; - const previousURI = activeTextEditor && activeTextEditor.document.uri; - const previousSelection = activeTextEditor && activeTextEditor.selection; + const previousVisibleRange = activeTextEditor?.visibleRanges[0]; + const previousURI = activeTextEditor?.document.uri; + const previousSelection = activeTextEditor?.selection; for (const uri of uris) { const opts: TextDocumentShowOptions = { From 093aa3c433320e9aaa32ca55fe8fc54e6fbe8336 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 2 Dec 2021 17:02:21 +0100 Subject: [PATCH 0275/2210] Allows editor options to override how new config updates are applied. --- .../common/config/commonEditorConfig.ts | 57 ++++++++----------- src/vs/editor/common/config/editorOptions.ts | 47 +++++++++++++++ .../standalone/browser/standaloneEditor.ts | 3 +- src/vs/monaco.d.ts | 10 ++++ 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index cbb3314229eef..45b3bd7bcb52a 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -112,12 +112,15 @@ class EditorConfiguration2 { } private static _deepEquals(a: T, b: T): boolean { - if (typeof a !== 'object' || typeof b !== 'object') { - return (a === b); + if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) { + return a === b; } if (Array.isArray(a) || Array.isArray(b)) { return (Array.isArray(a) && Array.isArray(b) ? arrays.equals(a, b) : false); } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } for (let key in a) { if (!EditorConfiguration2._deepEquals(a[key], b[key])) { return false; @@ -138,6 +141,22 @@ class EditorConfiguration2 { } return (somethingChanged ? new ConfigurationChangedEvent(result) : null); } + + /** + * Returns true if something changed. + * Modifies `options`. + */ + public static applyUpdate(options: IEditorOptions, update: Readonly): boolean { + let changed = false; + for (const editorOption of editorOptionsRegistry) { + if (update.hasOwnProperty(editorOption.name)) { + const result = editorOption.applyUpdate((options as any)[editorOption.name], (update as any)[editorOption.name]); + (options as any)[editorOption.name] = result.newValue; + changed = changed || result.didChange; + } + } + return changed; + } } /** @@ -382,43 +401,17 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC return EditorConfiguration2.computeOptions(this._validatedOptions, env); } - private static _subsetEquals(base: { [key: string]: any }, subset: { [key: string]: any }): boolean { - for (const key in subset) { - if (hasOwnProperty.call(subset, key)) { - const subsetValue = subset[key]; - const baseValue = base[key]; - - if (baseValue === subsetValue) { - continue; - } - if (Array.isArray(baseValue) && Array.isArray(subsetValue)) { - if (!arrays.equals(baseValue, subsetValue)) { - return false; - } - continue; - } - if (baseValue && typeof baseValue === 'object' && subsetValue && typeof subsetValue === 'object') { - if (!this._subsetEquals(baseValue, subsetValue)) { - return false; - } - continue; - } - - return false; - } - } - return true; - } - public updateOptions(_newOptions: Readonly): void { if (typeof _newOptions === 'undefined') { return; } const newOptions = deepCloneAndMigrateOptions(_newOptions); - if (CommonEditorConfiguration._subsetEquals(this._rawOptions, newOptions)) { + + const didChange = EditorConfiguration2.applyUpdate(this._rawOptions, newOptions); + if (!didChange) { return; } - this._rawOptions = objects.mixin(this._rawOptions, newOptions || {}); + this._readOptions = EditorConfiguration2.readOptions(this._rawOptions); this._validatedOptions = EditorConfiguration2.validateOptions(this._readOptions); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 98617176a0c02..78a887e21027c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -12,6 +12,8 @@ import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import * as arrays from 'vs/base/common/arrays'; +import * as objects from 'vs/base/common/objects'; //#region typed options @@ -806,6 +808,11 @@ export interface IEditorOption { * @internal */ compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V; + + /** + * Might modify `value`. + */ + applyUpdate(value: V, update: V): ApplyUpdateResult; } type PossibleKeyName0 = { [K in keyof IEditorOptions]: IEditorOptions[K] extends V | undefined ? K : never }[keyof IEditorOptions]; @@ -828,6 +835,10 @@ abstract class BaseEditorOption implements IEditorOp this.schema = schema; } + public applyUpdate(value: V, update: V): ApplyUpdateResult { + return applyUpdate(value, update); + } + public abstract validate(input: any): V; public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V { @@ -835,6 +846,34 @@ abstract class BaseEditorOption implements IEditorOp } } +export class ApplyUpdateResult { + constructor( + public readonly newValue: T, + public readonly didChange: boolean + ) { } +} + +function applyUpdate(value: T, update: T): ApplyUpdateResult { + if (typeof value !== 'object' || typeof update !== 'object') { + return new ApplyUpdateResult(update, value === update); + } + if (Array.isArray(value) || Array.isArray(update)) { + const arrayEquals = Array.isArray(value) && Array.isArray(update) && arrays.equals(value, update); + return new ApplyUpdateResult(update, arrayEquals); + } + let didChange = false; + for (let key in update) { + if ((value as T & object).hasOwnProperty(key)) { + const result = applyUpdate(value[key], update[key]); + if (result.didChange) { + value[key] = result.newValue; + didChange = true; + } + } + } + return new ApplyUpdateResult(value, didChange); +} + /** * @internal */ @@ -853,6 +892,10 @@ abstract class ComputedEditorOption implements IEdit this.deps = deps; } + public applyUpdate(value: V, update: V): ApplyUpdateResult { + return applyUpdate(value, update); + } + public validate(input: any): V { return this.defaultValue; } @@ -874,6 +917,10 @@ class SimpleEditorOption implements IEditorOption { + return applyUpdate(value, update); + } + public validate(input: any): V { if (typeof input === 'undefined') { return this.defaultValue; diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index a1c35fd98a9d0..438761bc36cdd 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { DiffNavigator, IDiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; -import { EditorOptions, ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; +import { EditorOptions, ConfigurationChangedEvent, ApplyUpdateResult } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { Token } from 'vs/editor/common/core/token'; import { IEditor, EditorType } from 'vs/editor/common/editorCommon'; @@ -393,6 +393,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { FontInfo: FontInfo, TextModelResolvedOptions: TextModelResolvedOptions, FindMatch: FindMatch, + ApplyUpdateResult: ApplyUpdateResult, // vars EditorType: EditorType, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 38f9d640e7e96..5d075b98e6e3f 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3378,6 +3378,16 @@ declare namespace monaco.editor { readonly id: K1; readonly name: string; defaultValue: V; + /** + * Might modify `value`. + */ + applyUpdate(value: V, update: V): ApplyUpdateResult; + } + + export class ApplyUpdateResult { + readonly newValue: T; + readonly didChange: boolean; + constructor(newValue: T, didChange: boolean); } /** From 469043a5611de427b4a5ba46b822ef3101a4f2c4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 2 Dec 2021 17:03:02 +0100 Subject: [PATCH 0276/2210] allowedCharacters: string -> allowedCharacters: Record --- src/vs/editor/common/config/editorOptions.ts | 48 ++++++++++++++----- .../unicodeHighlighter/unicodeHighlighter.ts | 34 +++++++------ src/vs/monaco.d.ts | 4 +- 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 78a887e21027c..c22822c02308c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -3312,9 +3312,9 @@ export interface IUnicodeHighlightOptions { ambiguousCharacters?: boolean; includeComments?: boolean | InUntrustedWorkspace; /** - * A list of allowed code points in a single string. + * A map of allowed characters (true: allowed). */ - allowedCharacters?: string; + allowedCharacters?: Record; } /** @@ -3340,7 +3340,7 @@ class UnicodeHighlight extends BaseEditorOption>, update: Required>): ApplyUpdateResult>> { + let didChange = false; + if (update.allowedCharacters) { + // Treat allowedCharacters atomically + if (!objects.equals(value.allowedCharacters, update.allowedCharacters)) { + value = { ...value, allowedCharacters: update.allowedCharacters }; + didChange = true; + } + } + + const result = super.applyUpdate(value, update); + if (didChange) { + return new ApplyUpdateResult(result.newValue, true); + } + return result; + } + public validate(_input: any): InternalUnicodeHighlightOptions { if (!_input || typeof _input !== 'object') { return this.defaultValue; @@ -3392,16 +3412,22 @@ class UnicodeHighlight extends BaseEditorOption(input.includeComments, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), - allowedCharacters: string(input.allowedCharacters, ''), + allowedCharacters: this.validateAllowedCharacters(_input.allowedCharacters, this.defaultValue.allowedCharacters), }; } -} -function string(value: unknown, defaultValue: string): string { - if (typeof value !== 'string') { - return defaultValue; + private validateAllowedCharacters(map: unknown, defaultValue: Record): Record { + if ((typeof map !== 'object') || !map) { + return defaultValue; + } + const result: Record = {}; + for (const [key, value] of Object.entries(map)) { + if (value === true) { + result[key] = true; + } + } + return result; } - return value; } //#endregion diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index 99a81fe5e2189..1d62875aa71b2 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -160,7 +160,7 @@ export class UnicodeHighlighter extends Disposable implements IEditorContributio ambiguousCharacters: options.ambiguousCharacters, invisibleCharacters: options.invisibleCharacters, includeComments: options.includeComments, - allowedCodePoints: Array.from(options.allowedCharacters).map(c => c.codePointAt(0)!), + allowedCodePoints: Object.keys(options.allowedCharacters).map(c => c.codePointAt(0)!), }; if (this._editorWorkerService.canComputeUnicodeHighlights(this._editor.getModel().uri)) { @@ -191,7 +191,7 @@ function resolveOptions(trusted: boolean, options: InternalUnicodeHighlightOptio ambiguousCharacters: options.ambiguousCharacters, invisibleCharacters: options.invisibleCharacters, includeComments: options.includeComments === inUntrustedWorkspace ? !trusted : options.includeComments, - allowedCharacters: options.allowedCharacters ?? [], + allowedCharacters: options.allowedCharacters ?? {}, }; } @@ -640,18 +640,7 @@ export class ShowExcludeOptions extends EditorAction { const options: ExtendedOptions[] = [ { label: getExcludeCharFromBeingHighlightedLabel(codePoint), - run: async () => { - const existingValue = configurationService.getValue(unicodeHighlightConfigKeys.allowedCharacters); - let value: string; - if (typeof existingValue === 'string') { - value = existingValue; - } else { - value = ''; - } - - value += char; - await configurationService.updateValue(unicodeHighlightConfigKeys.allowedCharacters, value, ConfigurationTarget.USER); - } + run: () => excludeCharFromBeingHighlighted(configurationService, [codePoint]) }, ]; @@ -681,6 +670,23 @@ export class ShowExcludeOptions extends EditorAction { } } +async function excludeCharFromBeingHighlighted(configurationService: IConfigurationService, charCodes: number[]) { + const existingValue = configurationService.getValue(unicodeHighlightConfigKeys.allowedCharacters); + + let value: Record; + if ((typeof existingValue === 'object') && existingValue) { + value = existingValue as any; + } else { + value = {}; + } + + for (const charCode of charCodes) { + value[String.fromCodePoint(charCode)] = true; + } + + await configurationService.updateValue(unicodeHighlightConfigKeys.allowedCharacters, value, ConfigurationTarget.USER); +} + function expectNever(value: never) { throw new Error(`Unexpected value: ${value}`); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 5d075b98e6e3f..16dc97937443f 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3884,9 +3884,9 @@ declare namespace monaco.editor { ambiguousCharacters?: boolean; includeComments?: boolean | InUntrustedWorkspace; /** - * A list of allowed code points in a single string. + * A map of allowed characters (true: allowed). */ - allowedCharacters?: string; + allowedCharacters?: Record; } export interface IInlineSuggestOptions { From bbede2dc0a60d7e4060e6b00c2a0dcb7f23e2166 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 2 Dec 2021 17:07:44 +0100 Subject: [PATCH 0277/2210] Add extension readme file --- extensions/git-base/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 extensions/git-base/README.md diff --git a/extensions/git-base/README.md b/extensions/git-base/README.md new file mode 100644 index 0000000000000..ff5bcc321c7e2 --- /dev/null +++ b/extensions/git-base/README.md @@ -0,0 +1,20 @@ +# Git static contributions and remote repository picker + +**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +## Features + +Git static contributions and remote repository picker. + +## API + +The Git extension exposes an API, reachable by any other extension. + +1. Copy `src/api/git-base.d.ts` to your extension's sources; +2. Include `git-base.d.ts` in your extension's compilation. +3. Get a hold of the API with the following snippet: + + ```ts + const gitBaseExtension = vscode.extensions.getExtension('vscode.git-base').exports; + const git = gitBaseExtension.getAPI(1); + ``` From a78af4e44dc3a12459064380f37dc89382fd70de Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 2 Dec 2021 17:26:04 +0100 Subject: [PATCH 0278/2210] Fixes test failures. --- src/vs/editor/common/config/editorOptions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index c22822c02308c..c7f6259e12dff 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -854,8 +854,8 @@ export class ApplyUpdateResult { } function applyUpdate(value: T, update: T): ApplyUpdateResult { - if (typeof value !== 'object' || typeof update !== 'object') { - return new ApplyUpdateResult(update, value === update); + if (typeof value !== 'object' || typeof update !== 'object' || !value || !update) { + return new ApplyUpdateResult(update, value !== update); } if (Array.isArray(value) || Array.isArray(update)) { const arrayEquals = Array.isArray(value) && Array.isArray(update) && arrays.equals(value, update); @@ -863,7 +863,7 @@ function applyUpdate(value: T, update: T): ApplyUpdateResult { } let didChange = false; for (let key in update) { - if ((value as T & object).hasOwnProperty(key)) { + if ((update as T & object).hasOwnProperty(key)) { const result = applyUpdate(value[key], update[key]); if (result.didChange) { value[key] = result.newValue; From 1303984d6a4cb32ca4c6be4161c8ad8f721d87b6 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 2 Dec 2021 08:29:04 -0800 Subject: [PATCH 0279/2210] Add `tarballProxyEndpoints` workbench option (#138283) --- src/vs/workbench/workbench.web.api.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 997ad303312f6..f981e44f2d846 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -412,6 +412,12 @@ interface IWorkbenchConstructionOptions { */ readonly codeExchangeProxyEndpoints?: { [providerId: string]: string } + /** + * [TEMPORARY]: This will be removed soon. + * Endpoints to be used for proxying repository tarball download calls in the browser. + */ + readonly _tarballProxyEndpoints?: { [providerId: string]: string } + //#endregion @@ -674,6 +680,8 @@ function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions) } } + CommandsRegistry.registerCommand('_workbench.getTarballProxyEndpoints', () => (options._tarballProxyEndpoints ?? {})); + // Startup workbench and resolve waiters let instantiatedWorkbench: IWorkbench | undefined = undefined; main(domElement, options).then(workbench => { From 0d61a576f79d63c605b5094b34af8935e74901dd Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 2 Dec 2021 17:35:21 +0100 Subject: [PATCH 0280/2210] Removes unused field ComputedEditorOption.deps --- src/vs/editor/common/config/editorOptions.ts | 21 +++++--------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 98617176a0c02..bee48ecd1d581 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -843,14 +843,12 @@ abstract class ComputedEditorOption implements IEdit public readonly id: K1; public readonly name: '_never_'; public readonly defaultValue: V; - public readonly deps: EditorOption[] | null; public readonly schema: IConfigurationPropertySchema | undefined = undefined; - constructor(id: K1, deps: EditorOption[] | null = null) { + constructor(id: K1) { this.id = id; this.name = '_never_'; this.defaultValue = undefined; - this.deps = deps; } public validate(input: any): V { @@ -1303,7 +1301,7 @@ function _cursorStyleFromString(cursorStyle: 'line' | 'block' | 'underline' | 'l class EditorClassName extends ComputedEditorOption { constructor() { - super(EditorOption.editorClassName, [EditorOption.mouseStyle, EditorOption.extraEditorClassName]); + super(EditorOption.editorClassName); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: string): string { @@ -2031,16 +2029,7 @@ export interface IMinimapLayoutInput { export class EditorLayoutInfoComputer extends ComputedEditorOption { constructor() { - super( - EditorOption.layoutInfo, - [ - EditorOption.glyphMargin, EditorOption.lineDecorationsWidth, EditorOption.folding, - EditorOption.minimap, EditorOption.scrollbar, EditorOption.lineNumbers, - EditorOption.lineNumbersMinChars, EditorOption.scrollBeyondLastLine, - EditorOption.wordWrap, EditorOption.wordWrapColumn, EditorOption.wordWrapOverride1, EditorOption.wordWrapOverride2, - EditorOption.accessibilitySupport - ] - ); + super(EditorOption.layoutInfo); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorLayoutInfo): EditorLayoutInfo { @@ -4103,7 +4092,7 @@ class SmartSelect extends BaseEditorOption { constructor() { - super(EditorOption.tabFocusMode, [EditorOption.readOnly]); + super(EditorOption.tabFocusMode); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: boolean): boolean { @@ -4161,7 +4150,7 @@ export interface EditorWrappingInfo { class EditorWrappingInfoComputer extends ComputedEditorOption { constructor() { - super(EditorOption.wrappingInfo, [EditorOption.layoutInfo]); + super(EditorOption.wrappingInfo); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorWrappingInfo): EditorWrappingInfo { From 0573777298ff29b19606818635752c7a702321a3 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 2 Dec 2021 11:00:24 -0800 Subject: [PATCH 0281/2210] input collapse should trigger view update as collapse is no longer metadata. --- .../contrib/notebook/browser/view/cellParts/markdownCell.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts index f1bf7ef082bbe..17bc0d1300200 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts @@ -138,6 +138,7 @@ export class StatefulMarkdownCell extends Disposable { if (e.inputCollapsedChanged) { this.updateCollapsedState(); + this.viewUpdate(); } if (e.cellLineNumberChanged) { From cca3e817a2608225d4ae8715308195c91f765bad Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 3 Dec 2021 00:05:45 +0100 Subject: [PATCH 0282/2210] Fix #136334 --- .../extensions/browser/extensionEditor.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 21ba089444663..918fe5f0e6d7f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/extensionEditor'; import { localize } from 'vs/nls'; -import { createCancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import * as arrays from 'vs/base/common/arrays'; import { OS } from 'vs/base/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; @@ -223,6 +223,7 @@ export class ExtensionEditor extends EditorPane { this.extensionEditorWidget = this._register(new class extends Disposable implements IExtensionEditorWidget { private gallery: IGalleryExtension | undefined = undefined; private extension: IExtension | undefined = undefined; + private updatePromise: CancelablePromise | undefined; constructor() { super(); this._register(that.extensionsWorkbenchService.onChange(e => { @@ -238,7 +239,8 @@ export class ExtensionEditor extends EditorPane { this.extension = extension; this.gallery = gallery; if (that.template) { - that.updateTemplate(this.extension, this.gallery, that.template, !!preserveFocus); + this.updatePromise?.cancel(); + this.updatePromise = createCancelablePromise(token => that.updateTemplate(this.extension!, this.gallery, that.template!, !!preserveFocus, token)); } } }()); @@ -408,7 +410,7 @@ export class ExtensionEditor extends EditorPane { } } - private async updateTemplate(extension: IExtension, gallery: IGalleryExtension | undefined, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise { + private async updateTemplate(extension: IExtension, gallery: IGalleryExtension | undefined, template: IExtensionEditorTemplate, preserveFocus: boolean, token: CancellationToken): Promise { this.activeElement = null; this.editorLoadComplete = false; @@ -419,9 +421,9 @@ export class ExtensionEditor extends EditorPane { this.transientDisposables.clear(); - this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(!!this.showPreReleaseVersionContextKey?.get(), token))); - this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(!!this.showPreReleaseVersionContextKey?.get(), token))); - this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(!!this.showPreReleaseVersionContextKey?.get(), token))); + this.extensionReadme = new Cache(() => extension.getReadme(!!this.showPreReleaseVersionContextKey?.get(), token)); + this.extensionChangelog = new Cache(() => extension.getChangelog(!!this.showPreReleaseVersionContextKey?.get(), token)); + this.extensionManifest = new Cache(() => extension.getManifest(!!this.showPreReleaseVersionContextKey?.get(), token)); const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, template.iconContainer, true); this.transientDisposables.add(addDisposableListener(template.icon, 'error', () => template.icon.src = extension.iconUrlFallback, { once: true })); @@ -474,6 +476,15 @@ export class ExtensionEditor extends EditorPane { })); } + const [colorThemes, fileIconThemes, productIconThemes] = await Promise.all([ + this.workbenchThemeService.getColorThemes(), + this.workbenchThemeService.getFileIconThemes(), + this.workbenchThemeService.getProductIconThemes(), + ]); + if (token.isCancellationRequested) { + return; + } + const widgets = [ remoteBadge, this.instantiationService.createInstance(InstallCountWidget, template.installCount, false), @@ -485,9 +496,9 @@ export class ExtensionEditor extends EditorPane { reloadAction, this.instantiationService.createInstance(ExtensionStatusLabelAction), this.instantiationService.createInstance(UpdateAction), - this.instantiationService.createInstance(SetColorThemeAction, await this.workbenchThemeService.getColorThemes()), - this.instantiationService.createInstance(SetFileIconThemeAction, await this.workbenchThemeService.getFileIconThemes()), - this.instantiationService.createInstance(SetProductIconThemeAction, await this.workbenchThemeService.getProductIconThemes()), + this.instantiationService.createInstance(SetColorThemeAction, colorThemes), + this.instantiationService.createInstance(SetFileIconThemeAction, fileIconThemes), + this.instantiationService.createInstance(SetProductIconThemeAction, productIconThemes), this.instantiationService.createInstance(EnableDropDownAction), this.instantiationService.createInstance(DisableDropDownAction), @@ -535,6 +546,10 @@ export class ExtensionEditor extends EditorPane { } const manifest = await this.extensionManifest.get().promise; + if (token.isCancellationRequested) { + return; + } + if (manifest) { combinedInstallAction.manifest = manifest; } From c9aab8fca7c3a1f5f3b0f8ac415a25bdb43fa4ed Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 3 Dec 2021 11:34:37 +0100 Subject: [PATCH 0283/2210] Fix tasks with linux to Windows remote Fixes microsoft/vscode-remote-release#5989 --- src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 3fd75cfb6db32..2add2cd37d21a 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1084,7 +1084,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let windowsShellArgs: boolean = false; if (platform === Platform.Platform.Windows) { windowsShellArgs = true; - let basename = path.posix.basename(URI.file(shellLaunchConfig.executable!).path).toLowerCase(); + let basename = path.posix.basename((await this.pathService.fileURI(shellLaunchConfig.executable!)).path).toLowerCase(); // If we don't have a cwd, then the terminal uses the home dir. const userHome = await this.pathService.userHome(); if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath)))) { From d931b945a98224b3bec7f081be72a35163b8e651 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 3 Dec 2021 09:13:01 -0500 Subject: [PATCH 0284/2210] Bump distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e16f53ff1ef9c..5b87e79334e2e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.63.0", - "distro": "d2c899d07c70f2580da86cebee758d8a3ff136d5", + "distro": "f6cb6296a1dd0c2a12e587b188a338073ce59799", "author": { "name": "Microsoft Corporation" }, From f0654a2f62f40ab3c13362a468be9ac67c7dc39c Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 3 Dec 2021 10:00:47 -0500 Subject: [PATCH 0285/2210] Fix #138361 --- build/lib/util.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build/lib/util.ts b/build/lib/util.ts index 01796a59f6739..585479d3ab21d 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -345,14 +345,17 @@ export function acquireWebNodePaths() { const root = path.join(__dirname, '..', '..'); const webPackageJSON = path.join(root, '/remote/web', 'package.json'); const webPackages = JSON.parse(fs.readFileSync(webPackageJSON, 'utf8')).dependencies; - const nodePaths: { [key: string]: string } = { }; + const nodePaths: { [key: string]: string } = {}; for (const key of Object.keys(webPackages)) { const packageJSON = path.join(root, 'node_modules', key, 'package.json'); const packageData = JSON.parse(fs.readFileSync(packageJSON, 'utf8')); let entryPoint = packageData.browser ?? packageData.main; // On rare cases a package doesn't have an entrypoint so we assume it has a dist folder with a min.js if (!entryPoint) { - console.warn(`No entry point for ${key} assuming dist/${key}.min.js`); + // TODO @lramos15 remove this when jschardet adds an entrypoint so we can warn on all packages w/out entrypoint + if (key !== 'jschardet') { + console.warn(`No entry point for ${key} assuming dist/${key}.min.js`); + } entryPoint = `dist/${key}.min.js`; } // Remove any starting path information so it's all relative info From f0f375d7eb4026dc842cbbf4811b94b9a9338f03 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 3 Dec 2021 08:23:00 -0800 Subject: [PATCH 0286/2210] add text clarifying when buttons can be used. Fixes #138204 --- src/vscode-dts/vscode.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index ff186b0103ca3..5f12f8b481990 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -1675,7 +1675,9 @@ declare module 'vscode' { /** * Optional buttons that will be rendered on this particular item. These buttons will trigger - * an {@link QuickPickItemButtonEvent} when clicked. + * an {@link QuickPickItemButtonEvent} when clicked. Buttons are only rendered when using a quickpick + * created by the {@link window.createQuickPick()} API. Buttons are not rendered when using + * the {@link window.showQuickPick()} API. */ buttons?: readonly QuickInputButton[]; } From f6da2394ae47046c2a02ce7fe03cf449026b6cf2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 3 Dec 2021 17:37:11 +0100 Subject: [PATCH 0287/2210] Remove browsing for file icon themes for security issues --- .../contrib/themes/browser/themes.contribution.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index f7554cfea4547..753e4e82d352c 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -241,7 +241,7 @@ class MarketplaceThemesPicker { class InstalledThemesPicker { constructor( private readonly installMessage: string, - private readonly browseMessage: string, + private readonly browseMessage: string | undefined, private readonly placeholderMessage: string, private readonly marketplaceTag: string, private readonly setTheme: (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => Promise, @@ -257,7 +257,7 @@ class InstalledThemesPicker { public async openQuickPick(picks: QuickPickInput[], currentTheme: IWorkbenchTheme) { let marketplaceThemePicker: MarketplaceThemesPicker | undefined; if (this.extensionGalleryService.isEnabled()) { - if (this.extensionResourceLoaderService.supportsExtensionGalleryResources) { + if (this.extensionResourceLoaderService.supportsExtensionGalleryResources && this.browseMessage) { marketplaceThemePicker = this.instantiationService.createInstance(MarketplaceThemesPicker, this.getMarketplaceColorThemes.bind(this), this.marketplaceTag); picks = [...configurationEntries(this.browseMessage), ...picks]; } else { @@ -399,14 +399,13 @@ registerAction2(class extends Action2 { const themeService = accessor.get(IWorkbenchThemeService); const installMessage = localize('installIconThemes', "Install Additional File Icon Themes..."); - const browseMessage = '$(plus) ' + localize('browseIconThemes', "Browse Additional File Icon Themes..."); const placeholderMessage = localize('themes.selectIconTheme', "Select File Icon Theme (Up/Down Keys to Preview)"); const marketplaceTag = 'tag:icon-theme'; const setTheme = (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => themeService.setFileIconTheme(theme as IWorkbenchFileIconTheme, settingsTarget); const getMarketplaceColorThemes = (publisher: string, name: string, version: string) => themeService.getMarketplaceFileIconThemes(publisher, name, version); const instantiationService = accessor.get(IInstantiationService); - const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, browseMessage, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes); + const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, undefined, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes); const picks: QuickPickInput[] = [ { type: 'separator', label: localize('fileIconThemeCategory', 'file icon themes') }, From 8541d5b225cf6847dd2cf45a43778e1047766702 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 3 Dec 2021 10:49:51 -0800 Subject: [PATCH 0288/2210] Improve docs on QuickPickItem.picked. Fixes #138070 --- src/vscode-dts/vscode.d.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 5f12f8b481990..08d8ec29761ca 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -1661,8 +1661,10 @@ declare module 'vscode' { detail?: string; /** - * Optional flag indicating if this item is picked initially. - * (Only honored when the picker allows multiple selections.) + * Optional flag indicating if this item is picked initially. This is only honored when using + * the {@link window.showQuickPick()} API. To do the same thing with the {@link window.createQuickPick()} API, + * simply set the {@link QuickPick.selectedItems} to the items you want picked initially. + * (*Note:* This is only honored when the picker allows multiple selections.) * * @see {@link QuickPickOptions.canPickMany} */ From d09289a2b6dd0e56b2a264a8e9380685498e10d6 Mon Sep 17 00:00:00 2001 From: Chris Dias Date: Fri, 3 Dec 2021 11:33:44 -0800 Subject: [PATCH 0289/2210] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b87e79334e2e..6d1fb7cca0c87 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.63.0", - "distro": "f6cb6296a1dd0c2a12e587b188a338073ce59799", + "distro": "28dea56425abcfafd4de9d5073e6fadfbf3518f5", "author": { "name": "Microsoft Corporation" }, From 4c98664778f5dcc9e40dff22d86c7a829528f7ca Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 08:39:39 +0100 Subject: [PATCH 0290/2210] editors - allow to close a dirty editor that is opened in another group split (fix #138442) --- .../browser/parts/editor/editorGroupView.ts | 14 +++++----- src/vs/workbench/common/editor.ts | 17 ++++++++++++ .../common/editor/editorGroupModel.ts | 27 ++++--------------- .../editor/common/editorGroupsService.ts | 5 ++-- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 7a2aa70b8ac8d..6f1fff939e1d0 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroupModel, IEditorOpenOptions, ISerializedEditorGroupModel, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; -import { GroupIdentifier, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, EditorResourceAccessor, IEditorMoveEvent, EditorInputCapabilities, IEditorOpenEvent, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, ActiveEditorGroupLockedContext, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent } from 'vs/workbench/common/editor'; +import { GroupIdentifier, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, EditorResourceAccessor, IEditorMoveEvent, EditorInputCapabilities, IEditorOpenEvent, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, ActiveEditorGroupLockedContext, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { Event, Emitter, Relay } from 'vs/base/common/event'; @@ -860,8 +860,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.model.isActive(editor); } - contains(candidate: EditorInput | IUntypedEditorInput): boolean { - return this.model.contains(candidate); + contains(candidate: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean { + return this.model.contains(candidate, options); } getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] { @@ -1556,15 +1556,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // if it is opened in a side-by-side editor in the group. This decision is made // because it may be less obvious that one side of a side by side editor is dirty // and can still be changed. + // The only exception is when the same editor is opened on both sides of a side + // by side editor (https://github.com/microsoft/vscode/issues/138442) if (this.accessor.groups.some(groupView => { if (groupView === this) { - return false; // skip this group to avoid false assumptions about the editor being opened still + return false; // skip (we already handled our group above) } const otherGroup = groupView; - if (otherGroup.contains(editor)) { - return true; // exact editor still opened + if (otherGroup.contains(editor, { supportSideBySide: SideBySideEditor.BOTH })) { + return true; // exact editor still opened (either single, or split-in-group) } if (editor instanceof SideBySideEditorInput && otherGroup.contains(editor.primary)) { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index a8719b6cea0d9..0e1cadf75fa98 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -884,6 +884,23 @@ export enum SideBySideEditor { ANY = 4 } +export interface IMatchEditorOptions { + + /** + * Whether to consider a side by side editor as matching. + * By default, side by side editors will not be considered + * as matching, even if the editor is opened in one of the sides. + */ + supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH; + + /** + * Only consider an editor to match when the + * `candidate === editor` but not when + * `candidate.matches(editor)`. + */ + strictEquals?: boolean; +} + export interface IEditorResourceAccessorOptions { /** diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 686f332c48db6..7a2857877b523 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { IEditorFactoryRegistry, GroupIdentifier, EditorsOrder, EditorExtensions, IUntypedEditorInput, SideBySideEditor, IEditorMoveEvent, IEditorOpenEvent, EditorCloseContext, IEditorCloseEvent } from 'vs/workbench/common/editor'; +import { IEditorFactoryRegistry, GroupIdentifier, EditorsOrder, EditorExtensions, IUntypedEditorInput, SideBySideEditor, IEditorMoveEvent, IEditorOpenEvent, EditorCloseContext, IEditorCloseEvent, IMatchEditorOptions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -53,23 +53,6 @@ export function isSerializedEditorGroupModel(group?: unknown): group is ISeriali return !!(candidate && typeof candidate === 'object' && Array.isArray(candidate.editors) && Array.isArray(candidate.mru)); } -export interface IMatchOptions { - - /** - * Whether to consider a side by side editor as matching. - * By default, side by side editors will not be considered - * as matching, even if the editor is opened in one of the sides. - */ - supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH; - - /** - * Only consider an editor to match when the - * `candidate === editor` but not when - * `candidate.matches(editor)`. - */ - strictEquals?: boolean; -} - export class EditorGroupModel extends Disposable { private static IDS = 0; @@ -696,7 +679,7 @@ export class EditorGroupModel extends Disposable { } } - indexOf(candidate: EditorInput | null, editors = this.editors, options?: IMatchOptions): number { + indexOf(candidate: EditorInput | null, editors = this.editors, options?: IMatchEditorOptions): number { let index = -1; if (candidate) { @@ -720,7 +703,7 @@ export class EditorGroupModel extends Disposable { return index; } - private findEditor(candidate: EditorInput | null, options?: IMatchOptions): [EditorInput, number /* index */] | undefined { + private findEditor(candidate: EditorInput | null, options?: IMatchEditorOptions): [EditorInput, number /* index */] | undefined { const index = this.indexOf(candidate, this.editors, options); if (index === -1) { return undefined; @@ -729,7 +712,7 @@ export class EditorGroupModel extends Disposable { return [this.editors[index], index]; } - contains(candidate: EditorInput | IUntypedEditorInput, options?: IMatchOptions): boolean { + contains(candidate: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean { for (const editor of this.editors) { if (this.matches(editor, candidate, options)) { return true; @@ -739,7 +722,7 @@ export class EditorGroupModel extends Disposable { return false; } - private matches(editor: EditorInput | null, candidate: EditorInput | IUntypedEditorInput | null, options?: IMatchOptions): boolean { + private matches(editor: EditorInput | null, candidate: EditorInput | IUntypedEditorInput | null, options?: IMatchEditorOptions): boolean { if (!editor || !candidate) { return false; } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index a0ec7b7eb71c7..1f1950465d837 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorPane, GroupIdentifier, EditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, isEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent } from 'vs/workbench/common/editor'; +import { IEditorPane, GroupIdentifier, EditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, isEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -620,8 +620,9 @@ export interface IEditorGroup { * Find out if a certain editor is included in the group. * * @param candidate the editor to find + * @param options fine tune how to match editors */ - contains(candidate: EditorInput | IUntypedEditorInput): boolean; + contains(candidate: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean; /** * Move an editor from this group either within this group or to another group. From 96643fc3f523cd8982d22b519c63aa5837764778 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 6 Dec 2021 09:52:41 +0100 Subject: [PATCH 0291/2210] update mywork milestone --- .vscode/notebooks/my-work.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index f207cf5e4b575..5401e27d2d6e6 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github\n\n// current milestone name\n$milestone=milestone:\"November 2021\"" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github\n\n// current milestone name\n$milestone=milestone:\"December 2021\"" }, { "kind": 1, From 84fce54d282ef7f2244b48bcd1788397368329ca Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 6 Dec 2021 10:35:48 +0100 Subject: [PATCH 0292/2210] send file system info with dummy uri, https://github.com/microsoft/vscode-remote-release/issues/5714 this ensures uris undergo transformation and therefore file and vscode-remotes are handled correctlly --- src/vs/workbench/api/browser/mainThreadFileSystem.ts | 6 +++--- src/vs/workbench/api/common/extHost.protocol.ts | 4 ++-- src/vs/workbench/api/common/extHostFileSystemInfo.ts | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 042e2264f5f0d..1e79801f68f6f 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -27,10 +27,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { const infoProxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemInfo); for (let entry of _fileService.listCapabilities()) { - infoProxy.$acceptProviderInfos(entry.scheme, entry.capabilities); + infoProxy.$acceptProviderInfos(URI.from({ scheme: entry.scheme, path: '/dummy' }), entry.capabilities); } - this._disposables.add(_fileService.onDidChangeFileSystemProviderRegistrations(e => infoProxy.$acceptProviderInfos(e.scheme, e.provider?.capabilities ?? null))); - this._disposables.add(_fileService.onDidChangeFileSystemProviderCapabilities(e => infoProxy.$acceptProviderInfos(e.scheme, e.provider.capabilities))); + this._disposables.add(_fileService.onDidChangeFileSystemProviderRegistrations(e => infoProxy.$acceptProviderInfos(URI.from({ scheme: e.scheme, path: '/dummy' }), e.provider?.capabilities ?? null))); + this._disposables.add(_fileService.onDidChangeFileSystemProviderCapabilities(e => infoProxy.$acceptProviderInfos(URI.from({ scheme: e.scheme, path: '/dummy' }), e.provider.capabilities))); } dispose(): void { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c9f87013bb4b1..f8ac5bec2fe99 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -295,7 +295,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, dragAndDropMimeTypes: string[] | undefined}): Promise; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, dragAndDropMimeTypes: string[] | undefined }): Promise; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise; $reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; @@ -1289,7 +1289,7 @@ export interface ExtHostWorkspaceShape { } export interface ExtHostFileSystemInfoShape { - $acceptProviderInfos(scheme: string, capabilities: number | null): void; + $acceptProviderInfos(uri: UriComponents, capabilities: number | null): void; } export interface ExtHostFileSystemShape { diff --git a/src/vs/workbench/api/common/extHostFileSystemInfo.ts b/src/vs/workbench/api/common/extHostFileSystemInfo.ts index d86fd1bf40476..2fa583c672e60 100644 --- a/src/vs/workbench/api/common/extHostFileSystemInfo.ts +++ b/src/vs/workbench/api/common/extHostFileSystemInfo.ts @@ -5,6 +5,7 @@ import { Schemas } from 'vs/base/common/network'; import { ExtUri, IExtUri } from 'vs/base/common/resources'; +import { UriComponents } from 'vs/base/common/uri'; import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostFileSystemInfoShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -33,11 +34,11 @@ export class ExtHostFileSystemInfo implements ExtHostFileSystemInfoShape { }); } - $acceptProviderInfos(scheme: string, capabilities: number | null): void { + $acceptProviderInfos(uri: UriComponents, capabilities: number | null): void { if (capabilities === null) { - this._providerInfo.delete(scheme); + this._providerInfo.delete(uri.scheme); } else { - this._providerInfo.set(scheme, capabilities); + this._providerInfo.set(uri.scheme, capabilities); } } From fb0f3d4f31a3f5bf7f70f29c489d5b72fa4d8b83 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 10:38:41 +0100 Subject: [PATCH 0293/2210] backups - prevent redundant backup on startup (fix #138497) --- .../common/workingCopyBackupService.ts | 118 ++++++++++++------ .../workingCopyBackupService.test.ts | 73 +++++++++++ 2 files changed, 151 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts index d12bc5df34612..1f6015ede681d 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -47,6 +47,13 @@ export class WorkingCopyBackupsModel { const backupSchemaFolderStat = await this.fileService.resolve(backupSchemaFolder.resource); // Remember known backups in our caches + // + // Note: this does NOT account for resolving + // associated meta data because that requires + // opening the backup and reading the meta + // preamble. Instead, when backups are actually + // resolved, the meta data will be added via + // additional `update` calls. if (backupSchemaFolderStat.children) { for (const backupForSchema of backupSchemaFolderStat.children) { if (!backupForSchema.isDirectory) { @@ -62,7 +69,17 @@ export class WorkingCopyBackupsModel { } add(resource: URI, versionId = 0, meta?: IWorkingCopyBackupMeta): void { - this.cache.set(resource, { versionId, meta: deepClone(meta) }); // make sure to not store original meta in our cache... + this.cache.set(resource, { + versionId, + meta: deepClone(meta) + }); + } + + update(resource: URI, meta?: IWorkingCopyBackupMeta): void { + const entry = this.cache.get(resource); + if (entry) { + entry.meta = deepClone(meta); + } } count(): number { @@ -111,7 +128,7 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ declare readonly _serviceBrand: undefined; - private impl: NativeWorkingCopyBackupServiceImpl | InMemoryWorkingCopyBackupService; + private impl: WorkingCopyBackupServiceImpl | InMemoryWorkingCopyBackupService; constructor( backupWorkspaceHome: URI | undefined, @@ -121,9 +138,9 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ this.impl = this.initialize(backupWorkspaceHome); } - private initialize(backupWorkspaceHome: URI | undefined): NativeWorkingCopyBackupServiceImpl | InMemoryWorkingCopyBackupService { + private initialize(backupWorkspaceHome: URI | undefined): WorkingCopyBackupServiceImpl | InMemoryWorkingCopyBackupService { if (backupWorkspaceHome) { - return new NativeWorkingCopyBackupServiceImpl(backupWorkspaceHome, this.fileService, this.logService); + return new WorkingCopyBackupServiceImpl(backupWorkspaceHome, this.fileService, this.logService); } return new InMemoryWorkingCopyBackupService(); @@ -132,7 +149,7 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ reinitialize(backupWorkspaceHome: URI | undefined): void { // Re-init implementation (unless we are running in-memory) - if (this.impl instanceof NativeWorkingCopyBackupServiceImpl) { + if (this.impl instanceof WorkingCopyBackupServiceImpl) { if (backupWorkspaceHome) { this.impl.initialize(backupWorkspaceHome); } else { @@ -145,8 +162,8 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ return this.impl.hasBackups(); } - hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number): boolean { - return this.impl.hasBackupSync(identifier, versionId); + hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number, meta?: IWorkingCopyBackupMeta): boolean { + return this.impl.hasBackupSync(identifier, versionId, meta); } backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadableStream | VSBufferReadable, versionId?: number, meta?: IWorkingCopyBackupMeta, token?: CancellationToken): Promise { @@ -174,7 +191,7 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ } } -class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBackupService { +class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBackupService { private static readonly PREAMBLE_END_MARKER = '\n'; private static readonly PREAMBLE_END_MARKER_CHARCODE = '\n'.charCodeAt(0); @@ -224,7 +241,7 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC } try { - const identifier = await this.resolveIdentifier(backupResource); + const identifier = await this.resolveIdentifier(backupResource, this.model); if (!identifier) { this.logService.warn(`Backup: Unable to read target URI of backup ${backupResource} for migration to new hash.`); continue; @@ -249,14 +266,14 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC return model.count() > 0; } - hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number): boolean { + hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number, meta?: IWorkingCopyBackupMeta): boolean { if (!this.model) { return false; } const backupResource = this.toBackupResource(identifier); - return this.model.has(backupResource, versionId); + return this.model.has(backupResource, versionId, meta); } async backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadable | VSBufferReadableStream, versionId?: number, meta?: IWorkingCopyBackupMeta, token?: CancellationToken): Promise { @@ -267,7 +284,8 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC const backupResource = this.toBackupResource(identifier); if (model.has(backupResource, versionId, meta)) { - return; // return early if backup version id matches requested one + // return early if backup version id matches requested one + return; } return this.ioOperationQueues.queueFor(backupResource).queue(async () => { @@ -275,11 +293,18 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC return; } + if (model.has(backupResource, versionId, meta)) { + // return early if backup version id matches requested one + // this can happen when multiple backup IO operations got + // scheduled, racing against each other. + return; + } + // Encode as: Resource + META-START + Meta + END // and respect max length restrictions in case // meta is too large. let preamble = this.createPreamble(identifier, meta); - if (preamble.length >= NativeWorkingCopyBackupServiceImpl.PREAMBLE_MAX_LENGTH) { + if (preamble.length >= WorkingCopyBackupServiceImpl.PREAMBLE_MAX_LENGTH) { preamble = this.createPreamble(identifier); } @@ -294,6 +319,7 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC backupBuffer = VSBuffer.concat([preambleBuffer, VSBuffer.fromString('')]); } + // Write backup via file service await this.fileService.writeFile(backupResource, backupBuffer); // Update model @@ -302,7 +328,7 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC } private createPreamble(identifier: IWorkingCopyIdentifier, meta?: IWorkingCopyBackupMeta): string { - return `${identifier.resource.toString()}${NativeWorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR}${JSON.stringify({ ...meta, typeId: identifier.typeId })}${NativeWorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER}`; + return `${identifier.resource.toString()}${WorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR}${JSON.stringify({ ...meta, typeId: identifier.typeId })}${WorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER}`; } async discardBackups(filter?: { except: IWorkingCopyIdentifier[] }): Promise { @@ -360,17 +386,17 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC async getBackups(): Promise { const model = await this.ready; - const backups = await Promise.all(model.get().map(backupResource => this.resolveIdentifier(backupResource))); + const backups = await Promise.all(model.get().map(backupResource => this.resolveIdentifier(backupResource, model))); return coalesce(backups); } - private async resolveIdentifier(backupResource: URI): Promise { + private async resolveIdentifier(backupResource: URI, model: WorkingCopyBackupsModel): Promise { // Read the entire backup preamble by reading up to // `PREAMBLE_MAX_LENGTH` in the backup file until // the `PREAMBLE_END_MARKER` is found - const backupPreamble = await this.readToMatchingString(backupResource, NativeWorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER, NativeWorkingCopyBackupServiceImpl.PREAMBLE_MAX_LENGTH); + const backupPreamble = await this.readToMatchingString(backupResource, WorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER, WorkingCopyBackupServiceImpl.PREAMBLE_MAX_LENGTH); if (!backupPreamble) { return undefined; } @@ -378,7 +404,7 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC // Figure out the offset in the preamble where meta // information possibly starts. This can be `-1` for // older backups without meta. - const metaStartIndex = backupPreamble.indexOf(NativeWorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR); + const metaStartIndex = backupPreamble.indexOf(WorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR); // Extract the preamble content for resource and meta let resourcePreamble: string; @@ -391,15 +417,12 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC metaPreamble = undefined; } - // Try to find the `typeId` in the meta data if possible - let typeId: string | undefined = undefined; - if (metaPreamble) { - try { - typeId = JSON.parse(metaPreamble).typeId; - } catch (error) { - // ignore JSON parse errors - } - } + // Try to parse the meta preamble for figuring out + // `typeId` and `meta` if defined. + const { typeId, meta } = this.parsePreambleMeta(metaPreamble); + + // Update model entry with now resolved meta + model.update(backupResource, meta); return { typeId: typeId ?? NO_TYPE_ID, @@ -438,7 +461,7 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC // it always first gets truncated and then written to. In this case, we will not find // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out // here if that is the case. - const preambleEndIndex = firstBackupChunk.buffer.indexOf(NativeWorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER_CHARCODE); + const preambleEndIndex = firstBackupChunk.buffer.indexOf(WorkingCopyBackupServiceImpl.PREAMBLE_END_MARKER_CHARCODE); if (preambleEndIndex === -1) { this.logService.trace(`Backup: Could not find meta end marker in ${backupResource}. The file is probably corrupt (filesize: ${backupStream.size}).`); @@ -449,10 +472,34 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC // Extract meta data (if any) let meta: T | undefined; - const metaStartIndex = preambelRaw.indexOf(NativeWorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR); + const metaStartIndex = preambelRaw.indexOf(WorkingCopyBackupServiceImpl.PREAMBLE_META_SEPARATOR); if (metaStartIndex !== -1) { + meta = this.parsePreambleMeta(preambelRaw.substr(metaStartIndex + 1)).meta as T; + } + + // Update model entry with now resolved meta + model.update(backupResource, meta); + + // Build a new stream without the preamble + const firstBackupChunkWithoutPreamble = firstBackupChunk.slice(preambleEndIndex + 1); + let value: VSBufferReadableStream; + if (peekedBackupStream.ended) { + value = bufferToStream(firstBackupChunkWithoutPreamble); + } else { + value = prefixedBufferStream(firstBackupChunkWithoutPreamble, peekedBackupStream.stream); + } + + return { value, meta }; + } + + private parsePreambleMeta(preambleMetaRaw: string | undefined): { typeId: string | undefined, meta: T | undefined } { + let typeId: string | undefined = undefined; + let meta: T | undefined = undefined; + + if (preambleMetaRaw) { try { - meta = JSON.parse(preambelRaw.substr(metaStartIndex + 1)); + meta = JSON.parse(preambleMetaRaw); + typeId = meta?.typeId; // `typeId` is a property that we add so we // remove it when returning to clients. @@ -468,16 +515,7 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC } } - // Build a new stream without the preamble - const firstBackupChunkWithoutPreamble = firstBackupChunk.slice(preambleEndIndex + 1); - let value: VSBufferReadableStream; - if (peekedBackupStream.ended) { - value = bufferToStream(firstBackupChunkWithoutPreamble); - } else { - value = prefixedBufferStream(firstBackupChunkWithoutPreamble, peekedBackupStream.stream); - } - - return { value, meta }; + return { typeId, meta }; } toBackupResource(identifier: IWorkingCopyIdentifier): URI { diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts index 98bdf4eb720e9..b47f98ae0de55 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts @@ -492,6 +492,23 @@ flakySuite('WorkingCopyBackupService', () => { assert.ok(!service.hasBackupSync(identifier)); }); + test('multiple', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await Promise.all([ + service.backup(identifier), + service.backup(identifier), + service.backup(identifier), + service.backup(identifier) + ]); + + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier)); + assert.ok(service.hasBackupSync(identifier)); + }); + test('multiple same resource, different type id', async () => { const backupId1 = toUntypedWorkingCopyId(fooFile); const backupId2 = toTypedWorkingCopyId(fooFile, 'type1'); @@ -957,6 +974,54 @@ flakySuite('WorkingCopyBackupService', () => { assert.strictEqual(backup.meta, undefined); } + test('should update metadata from file into model when resolving', async () => { + await testShouldUpdateMetaFromFileWhenResolving(toUntypedWorkingCopyId(fooFile)); + await testShouldUpdateMetaFromFileWhenResolving(toTypedWorkingCopyId(fooFile)); + }); + + async function testShouldUpdateMetaFromFileWhenResolving(identifier: IWorkingCopyIdentifier): Promise { + const contents = 'Foo Bar'; + + const meta = { + etag: 'theEtagForThisMetadataTest', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + const updatedMeta = { + ...meta, + etag: meta.etag + meta.etag + }; + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString(contents)), 1, meta); + + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + // Simulate the condition of the backups model loading initially without + // meta data information and then getting the meta data updated on the + // first call to resolve the backup. We simulate this by explicitly changing + // the meta data in the file and then verifying that the updated meta data + // is persisted back into the model (verified via `hasBackupSync`). + // This is not really something that would happen in real life because any + // backup that is made via backup service will update the model accordingly. + + const originalFileContents = readFileSync(backupPath).toString(); + writeFileSync(backupPath, originalFileContents.replace(meta.etag, updatedMeta.etag)); + + await service.resolve(identifier); + + assert.strictEqual(service.hasBackupSync(identifier, undefined, meta), false); + assert.strictEqual(service.hasBackupSync(identifier, undefined, updatedMeta), true); + + writeFileSync(backupPath, originalFileContents); + + await service.getBackups(); + + assert.strictEqual(service.hasBackupSync(identifier, undefined, meta), true); + assert.strictEqual(service.hasBackupSync(identifier, undefined, updatedMeta), false); + } + test('should ignore invalid backups (empty file)', async () => { const contents = 'test\nand more stuff'; @@ -1059,6 +1124,14 @@ flakySuite('WorkingCopyBackupService', () => { assert.strictEqual(model.has(resource4, undefined, { foo: 'bar' }), true); assert.strictEqual(model.has(resource4, undefined, { bar: 'foo' }), false); + model.update(resource4, { foo: 'nothing' }); + assert.strictEqual(model.has(resource4, undefined, { foo: 'nothing' }), true); + assert.strictEqual(model.has(resource4, undefined, { foo: 'bar' }), false); + + model.update(resource4); + assert.strictEqual(model.has(resource4), true); + assert.strictEqual(model.has(resource4, undefined, { foo: 'nothing' }), false); + const resource5 = URI.file('test4.html'); model.move(resource4, resource5); assert.strictEqual(model.has(resource4), false); From cd648da82cd62db2d61f9e8333f798d1b261b719 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 10:46:51 +0100 Subject: [PATCH 0294/2210] sandbox - drop support for running file service in renderer --- .../electron-browser/workbench/workbench.js | 2 +- .../electron-sandbox/workbench/workbench.html | 2 +- .../files/node/diskFileSystemProvider.ts | 16 +- src/vs/platform/windows/common/windows.ts | 1 - .../electron-main/windowsMainService.ts | 1 - .../files/browser/files.contribution.ts | 8 +- .../browser/relauncher.contribution.ts | 9 +- .../electron-browser/desktop.main.ts | 47 --- .../electron-sandbox/desktop.main.ts | 381 ++++++++++++++++-- .../electron-sandbox/shared.desktop.main.ts | 378 ----------------- .../diskFileSystemProvider.ts | 49 --- src/vs/workbench/workbench.desktop.main.ts | 7 - .../workbench.desktop.sandbox.main.ts | 8 + src/vs/workbench/workbench.sandbox.main.ts | 7 + 14 files changed, 383 insertions(+), 533 deletions(-) delete mode 100644 src/vs/workbench/electron-browser/desktop.main.ts delete mode 100644 src/vs/workbench/electron-sandbox/shared.desktop.main.ts delete mode 100644 src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index f0235cf89a1f9..d83fb9abcd32c 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -29,7 +29,7 @@ performance.mark('code/didLoadWorkbenchMain'); // @ts-ignore - return require('vs/workbench/electron-browser/desktop.main').main(configuration); + return require('vs/workbench/electron-sandbox/desktop.main').main(configuration); }, { configureDeveloperSettings: function (windowConfig) { diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index f76e077595219..006d06ac4a9ea 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 40a9029b88e75..26d03507a60c0 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -83,7 +83,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple readonly onDidChangeCapabilities: Event = Event.None; - protected _capabilities: FileSystemProviderCapabilities | undefined; + private _capabilities: FileSystemProviderCapabilities | undefined; get capabilities(): FileSystemProviderCapabilities { if (!this._capabilities) { this._capabilities = @@ -454,20 +454,16 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple try { const filePath = this.toFilePath(resource); - await this.doDelete(filePath, opts); + if (opts.recursive) { + await Promises.rm(filePath, RimRafMode.MOVE); + } else { + await Promises.unlink(filePath); + } } catch (error) { throw this.toFileSystemProviderError(error); } } - protected async doDelete(filePath: string, opts: FileDeleteOptions): Promise { - if (opts.recursive) { - await Promises.rm(filePath, RimRafMode.MOVE); - } else { - await Promises.unlink(filePath); - } - } - async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { const fromFilePath = this.toFilePath(from); const toFilePath = this.toFilePath(to); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 20f12e9bc7da8..a8bb4b1a4a755 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -305,7 +305,6 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native autoDetectHighContrast?: boolean; legacyWatcher?: string; // TODO@bpasero remove me once watcher is settled - experimentalSandboxedFileService?: boolean; // TODO@bpasero remove me once sandbox is settled perfMarks: PerformanceMark[]; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 17b6c9540eb51..06a37000fab4f 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1294,7 +1294,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic zoomLevel: typeof windowConfig?.zoomLevel === 'number' ? windowConfig.zoomLevel : undefined, legacyWatcher: this.configurationService.getValue('files.legacyWatcher'), - experimentalSandboxedFileService: this.configurationService.getValue('files.experimentalSandboxedFileService'), autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true, accessibilitySupport: app.accessibilitySupportEnabled, colorScheme: { diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index af890396dd6b3..a64babf6f964d 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -307,13 +307,7 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'description': nls.localize('files.simpleDialog.enable', "Enables the simple file dialog. The simple file dialog replaces the system file dialog when enabled."), 'default': false - }, - 'files.experimentalSandboxedFileService': { - 'type': 'boolean', - 'description': nls.localize('files.experimentalSandboxedFileService', "Experimental: changes the file service to be sandboxed. Do not change this unless instructed!"), - 'default': true, - 'scope': ConfigurationScope.APPLICATION - }, + } } }); diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 34c9e0741221c..9e3101852830a 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -26,7 +26,7 @@ interface IConfiguration extends IWindowsConfiguration { debug?: { console?: { wordWrap?: boolean } }; editor?: { accessibilitySupport?: 'on' | 'off' | 'auto' }; security?: { workspace?: { trust?: { enabled?: boolean } } }; - files?: { legacyWatcher?: string, experimentalSandboxedFileService?: boolean }; + files?: { legacyWatcher?: string }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -39,7 +39,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; private workspaceTrustEnabled: boolean | undefined; private legacyFileWatcher: string | undefined = undefined; - private experimentalSandboxedFileService: boolean | undefined = undefined; constructor( @IHostService private readonly hostService: IHostService, @@ -107,12 +106,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo this.legacyFileWatcher = config.files.legacyWatcher; changed = true; } - - // Experimental Sandboxed File Service - if (typeof config.files?.experimentalSandboxedFileService === 'boolean' && config.files.experimentalSandboxedFileService !== this.experimentalSandboxedFileService) { - this.experimentalSandboxedFileService = config.files.experimentalSandboxedFileService; - changed = true; - } } // Notify only when changed and we are the focused window (avoids notification spam across windows) diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts deleted file mode 100644 index 3f9fb0e041f5a..0000000000000 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { INativeWorkbenchConfiguration } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Schemas } from 'vs/base/common/network'; -import { IFileService } from 'vs/platform/files/common/files'; -import { DiskFileSystemProvider as ElectronFileSystemProvider } from 'vs/workbench/services/files/electron-browser/diskFileSystemProvider'; -import { DiskFileSystemProvider as SandboxedDiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider'; -import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { ISharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService'; - -class DesktopMain extends SharedDesktopMain { - - protected registerFileSystemProviders( - mainProcessService: IMainProcessService, - sharedProcessWorkerWorkbenchService: ISharedProcessWorkerWorkbenchService, - fileService: IFileService, - logService: ILogService, - nativeHostService: INativeHostService - ): void { - - // Local Files - let diskFileSystemProvider: ElectronFileSystemProvider | SandboxedDiskFileSystemProvider; - if (this.configuration.experimentalSandboxedFileService !== false) { - diskFileSystemProvider = this._register(new SandboxedDiskFileSystemProvider(mainProcessService, sharedProcessWorkerWorkbenchService, logService)); - } else { - logService.info('[FileService]: NOT using sandbox ready file system provider'); - diskFileSystemProvider = this._register(new ElectronFileSystemProvider(logService, nativeHostService, { legacyWatcher: this.configuration.legacyWatcher })); - } - fileService.registerProvider(Schemas.file, diskFileSystemProvider); - - // User Data Provider - fileService.registerProvider(Schemas.userData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService))); - } -} - -export function main(configuration: INativeWorkbenchConfiguration): Promise { - const workbench = new DesktopMain(configuration); - - return workbench.open(); -} diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 207d4d6971603..ce36efb17b042 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -3,28 +3,227 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INativeWorkbenchConfiguration } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Schemas } from 'vs/base/common/network'; +import product from 'vs/platform/product/common/product'; +import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows'; +import { Workbench } from 'vs/workbench/browser/workbench'; +import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; +import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; +import { domContentLoaded } from 'vs/base/browser/dom'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; +import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; +import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { ILoggerService, ILogService } from 'vs/platform/log/common/log'; +import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IMainProcessService, ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { SharedProcessService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService'; +import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService'; +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; -import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel'; +import { ConfigurationCache } from 'vs/workbench/services/configuration/common/configurationCache'; +import { ISignService } from 'vs/platform/sign/common/sign'; +import { basename } from 'vs/base/common/path'; +import { IProductService } from 'vs/platform/product/common/productService'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { ISharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService'; +import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout'; +import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout'; +import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { LoggerChannelClient, LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { NativeLogService } from 'vs/workbench/services/log/electron-sandbox/logService'; +import { WorkspaceTrustEnablementService, WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver'; +import { safeStringify } from 'vs/base/common/objects'; +import { ISharedProcessWorkerWorkbenchService, SharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService'; +import { isMacintosh } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider'; -import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; + +export class DesktopMain extends Disposable { + + constructor( + protected readonly configuration: INativeWorkbenchConfiguration + ) { + super(); + + this.init(); + } + + private init(): void { + + // Massage configuration file URIs + this.reviveUris(); + + // Browser config + const zoomLevel = this.configuration.zoomLevel || 0; + setZoomFactor(zoomLevelToZoomFactor(zoomLevel)); + setZoomLevel(zoomLevel, true /* isTrusted */); + setFullscreen(!!this.configuration.fullscreen); + } + + private reviveUris() { + + // Workspace + const workspace = reviveIdentifier(this.configuration.workspace); + if (isWorkspaceIdentifier(workspace) || isSingleFolderWorkspaceIdentifier(workspace)) { + this.configuration.workspace = workspace; + } + + // Files + const filesToWait = this.configuration.filesToWait; + const filesToWaitPaths = filesToWait?.paths; + [filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => { + if (Array.isArray(paths)) { + paths.forEach(path => { + if (path.fileUri) { + path.fileUri = URI.revive(path.fileUri); + } + }); + } + }); + + if (filesToWait) { + filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri); + } + } + + async open(): Promise { + + // Init services and wait for DOM to be ready in parallel + const [services] = await Promise.all([this.initServices(), domContentLoaded()]); + + // Create Workbench + const workbench = new Workbench(document.body, { extraClasses: this.getExtraClasses() }, services.serviceCollection, services.logService); + + // Listeners + this.registerListeners(workbench, services.storageService); + + // Startup + const instantiationService = workbench.startup(); + + // Window + this._register(instantiationService.createInstance(NativeWindow)); + + // Logging + services.logService.trace('workbench configuration', safeStringify(this.configuration)); + + // Driver + if (this.configuration.driver) { + instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor, this.configuration.windowId))); + } + } + + private getExtraClasses(): string[] { + if (isMacintosh) { + if (this.configuration.os.release > '20.0.0') { + return ['macos-bigsur-or-newer']; + } + } + + return []; + } + + private registerListeners(workbench: Workbench, storageService: NativeStorageService): void { + + // Workbench Lifecycle + this._register(workbench.onWillShutdown(event => event.join(storageService.close(), 'join.closeStorage'))); + this._register(workbench.onDidShutdown(() => this.dispose())); + } + + private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> { + const serviceCollection = new ServiceCollection(); + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // + // NOTE: Please do NOT register services here. Use `registerSingleton()` + // from `workbench.common.main.ts` if the service is shared between + // desktop and web or `workbench.sandbox.main.ts` if the service + // is desktop only. + // + // DO NOT add services to `workbench.desktop.main.ts`, always add + // to `workbench.sandbox.main.ts` to support our Electron sandbox + // + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + + // Main Process + const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId)); + serviceCollection.set(IMainProcessService, mainProcessService); + + // Product + const productService: IProductService = { _serviceBrand: undefined, ...product }; + serviceCollection.set(IProductService, productService); + + // Environment + const environmentService = new NativeWorkbenchEnvironmentService(this.configuration, productService); + serviceCollection.set(INativeWorkbenchEnvironmentService, environmentService); + + // Logger + const logLevelChannelClient = new LogLevelChannelClient(mainProcessService.getChannel('logLevel')); + const loggerService = new LoggerChannelClient(environmentService.configuration.logLevel, logLevelChannelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger')); + serviceCollection.set(ILoggerService, loggerService); + + // Log + const logService = this._register(new NativeLogService(`renderer${this.configuration.windowId}`, environmentService.configuration.logLevel, loggerService, logLevelChannelClient, environmentService)); + serviceCollection.set(ILogService, logService); + + // Shared Process + const sharedProcessService = new SharedProcessService(this.configuration.windowId, logService); + serviceCollection.set(ISharedProcessService, sharedProcessService); + + // Shared Process Worker + const sharedProcessWorkerWorkbenchService = new SharedProcessWorkerWorkbenchService(this.configuration.windowId, logService, sharedProcessService); + serviceCollection.set(ISharedProcessWorkerWorkbenchService, sharedProcessWorkerWorkbenchService); + + // Remote + const remoteAuthorityResolverService = new RemoteAuthorityResolverService(); + serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // + // NOTE: Please do NOT register services here. Use `registerSingleton()` + // from `workbench.common.main.ts` if the service is shared between + // desktop and web or `workbench.sandbox.main.ts` if the service + // is desktop only. + // + // DO NOT add services to `workbench.desktop.main.ts`, always add + // to `workbench.sandbox.main.ts` to support our Electron sandbox + // + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + -class DesktopMain extends SharedDesktopMain { + // Sign + const signService = ProxyChannel.toService(mainProcessService.getChannel('sign')); + serviceCollection.set(ISignService, signService); - protected registerFileSystemProviders( - mainProcessService: IMainProcessService, - sharedProcessWorkerWorkbenchService: ISharedProcessWorkerWorkbenchService, - fileService: IFileService, - logService: ILogService, - nativeHostService: INativeHostService - ): void { + // Remote Agent + const remoteAgentService = this._register(new RemoteAgentService(environmentService, productService, remoteAuthorityResolverService, signService, logService)); + serviceCollection.set(IRemoteAgentService, remoteAgentService); + + // Native Host + const nativeHostService = new NativeHostService(this.configuration.windowId, mainProcessService) as INativeHostService; + serviceCollection.set(INativeHostService, nativeHostService); + + // Files + const fileService = this._register(new FileService(logService)); + serviceCollection.set(IFileService, fileService); // Local Files const diskFileSystemProvider = this._register(new DiskFileSystemProvider(mainProcessService, sharedProcessWorkerWorkbenchService, logService)); @@ -32,6 +231,148 @@ class DesktopMain extends SharedDesktopMain { // User Data Provider fileService.registerProvider(Schemas.userData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService))); + + // URI Identity + const uriIdentityService = new UriIdentityService(fileService); + serviceCollection.set(IUriIdentityService, uriIdentityService); + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // + // NOTE: Please do NOT register services here. Use `registerSingleton()` + // from `workbench.common.main.ts` if the service is shared between + // desktop and web or `workbench.sandbox.main.ts` if the service + // is desktop only. + // + // DO NOT add services to `workbench.desktop.main.ts`, always add + // to `workbench.sandbox.main.ts` to support our Electron sandbox + // + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // Remote file system + this._register(RemoteFileSystemProvider.register(remoteAgentService, fileService, logService)); + + const payload = this.resolveWorkspaceInitializationPayload(environmentService); + + const [configurationService, storageService] = await Promise.all([ + this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { + + // Workspace + serviceCollection.set(IWorkspaceContextService, service); + + // Configuration + serviceCollection.set(IWorkbenchConfigurationService, service); + + return service; + }), + + this.createStorageService(payload, environmentService, mainProcessService).then(service => { + + // Storage + serviceCollection.set(IStorageService, service); + + return service; + }), + + this.createKeyboardLayoutService(mainProcessService).then(service => { + + // KeyboardLayout + serviceCollection.set(IKeyboardLayoutService, service); + + return service; + }) + ]); + + // Workspace Trust Service + const workspaceTrustEnablementService = new WorkspaceTrustEnablementService(configurationService, environmentService); + serviceCollection.set(IWorkspaceTrustEnablementService, workspaceTrustEnablementService); + + const workspaceTrustManagementService = new WorkspaceTrustManagementService(configurationService, remoteAuthorityResolverService, storageService, uriIdentityService, environmentService, configurationService, workspaceTrustEnablementService); + serviceCollection.set(IWorkspaceTrustManagementService, workspaceTrustManagementService); + + // Update workspace trust so that configuration is updated accordingly + configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkspaceTrusted()); + this._register(workspaceTrustManagementService.onDidChangeTrust(() => configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkspaceTrusted()))); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // + // NOTE: Please do NOT register services here. Use `registerSingleton()` + // from `workbench.common.main.ts` if the service is shared between + // desktop and web or `workbench.sandbox.main.ts` if the service + // is desktop only. + // + // DO NOT add services to `workbench.desktop.main.ts`, always add + // to `workbench.sandbox.main.ts` to support our Electron sandbox + // + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + + return { serviceCollection, logService, storageService }; + } + + private resolveWorkspaceInitializationPayload(environmentService: INativeWorkbenchEnvironmentService): IWorkspaceInitializationPayload { + let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined = this.configuration.workspace; + + // Fallback to empty workspace if we have no payload yet. + if (!workspaceInitializationPayload) { + let id: string; + if (this.configuration.backupPath) { + // we know the backupPath must be a unique path so we leverage its name as workspace ID + id = basename(this.configuration.backupPath); + } else if (environmentService.isExtensionDevelopment) { + // fallback to a reserved identifier when in extension development where backups are not stored + id = 'ext-dev'; + } else { + throw new Error('Unexpected window configuration without backupPath'); + } + + workspaceInitializationPayload = { id }; + } + + return workspaceInitializationPayload; + } + + private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { + const configurationCache = new ConfigurationCache([Schemas.file, Schemas.userData] /* Cache all non native resources */, environmentService, fileService); + const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); + + try { + await workspaceService.initialize(payload); + + return workspaceService; + } catch (error) { + onUnexpectedError(error); + + return workspaceService; + } + } + + private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, mainProcessService: IMainProcessService): Promise { + const storageService = new NativeStorageService(payload, mainProcessService, environmentService); + + try { + await storageService.initialize(); + + return storageService; + } catch (error) { + onUnexpectedError(error); + + return storageService; + } + } + + private async createKeyboardLayoutService(mainProcessService: IMainProcessService): Promise { + const keyboardLayoutService = new KeyboardLayoutService(mainProcessService); + + try { + await keyboardLayoutService.initialize(); + + return keyboardLayoutService; + } catch (error) { + onUnexpectedError(error); + + return keyboardLayoutService; + } } } @@ -40,9 +381,3 @@ export function main(configuration: INativeWorkbenchConfiguration): Promise { - if (Array.isArray(paths)) { - paths.forEach(path => { - if (path.fileUri) { - path.fileUri = URI.revive(path.fileUri); - } - }); - } - }); - - if (filesToWait) { - filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri); - } - } - - async open(): Promise { - - // Init services and wait for DOM to be ready in parallel - const [services] = await Promise.all([this.initServices(), domContentLoaded()]); - - // Create Workbench - const workbench = new Workbench(document.body, { extraClasses: this.getExtraClasses() }, services.serviceCollection, services.logService); - - // Listeners - this.registerListeners(workbench, services.storageService); - - // Startup - const instantiationService = workbench.startup(); - - // Window - this._register(instantiationService.createInstance(NativeWindow)); - - // Logging - services.logService.trace('workbench configuration', safeStringify(this.configuration)); - - // Driver - if (this.configuration.driver) { - instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor, this.configuration.windowId))); - } - } - - private getExtraClasses(): string[] { - if (isMacintosh) { - if (this.configuration.os.release > '20.0.0') { - return ['macos-bigsur-or-newer']; - } - } - - return []; - } - - private registerListeners(workbench: Workbench, storageService: NativeStorageService): void { - - // Workbench Lifecycle - this._register(workbench.onWillShutdown(event => event.join(storageService.close(), 'join.closeStorage'))); - this._register(workbench.onDidShutdown(() => this.dispose())); - } - - protected abstract registerFileSystemProviders( - mainProcessService: IMainProcessService, - sharedProcessWorkerWorkbenchService: ISharedProcessWorkerWorkbenchService, - fileService: IFileService, - logService: ILogService, - nativeHostService: INativeHostService - ): void; - - private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> { - const serviceCollection = new ServiceCollection(); - - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // - // NOTE: Please do NOT register services here. Use `registerSingleton()` - // from `workbench.common.main.ts` if the service is shared between - // desktop and web or `workbench.sandbox.main.ts` if the service - // is desktop only. - // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox - // - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - - // Main Process - const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId)); - serviceCollection.set(IMainProcessService, mainProcessService); - - // Product - const productService: IProductService = { _serviceBrand: undefined, ...product }; - serviceCollection.set(IProductService, productService); - - // Environment - const environmentService = new NativeWorkbenchEnvironmentService(this.configuration, productService); - serviceCollection.set(INativeWorkbenchEnvironmentService, environmentService); - - // Logger - const logLevelChannelClient = new LogLevelChannelClient(mainProcessService.getChannel('logLevel')); - const loggerService = new LoggerChannelClient(environmentService.configuration.logLevel, logLevelChannelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger')); - serviceCollection.set(ILoggerService, loggerService); - - // Log - const logService = this._register(new NativeLogService(`renderer${this.configuration.windowId}`, environmentService.configuration.logLevel, loggerService, logLevelChannelClient, environmentService)); - serviceCollection.set(ILogService, logService); - - // Shared Process - const sharedProcessService = new SharedProcessService(this.configuration.windowId, logService); - serviceCollection.set(ISharedProcessService, sharedProcessService); - - // Shared Process Worker - const sharedProcessWorkerWorkbenchService = new SharedProcessWorkerWorkbenchService(this.configuration.windowId, logService, sharedProcessService); - serviceCollection.set(ISharedProcessWorkerWorkbenchService, sharedProcessWorkerWorkbenchService); - - // Remote - const remoteAuthorityResolverService = new RemoteAuthorityResolverService(); - serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); - - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // - // NOTE: Please do NOT register services here. Use `registerSingleton()` - // from `workbench.common.main.ts` if the service is shared between - // desktop and web or `workbench.sandbox.main.ts` if the service - // is desktop only. - // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox - // - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - - // Sign - const signService = ProxyChannel.toService(mainProcessService.getChannel('sign')); - serviceCollection.set(ISignService, signService); - - // Remote Agent - const remoteAgentService = this._register(new RemoteAgentService(environmentService, productService, remoteAuthorityResolverService, signService, logService)); - serviceCollection.set(IRemoteAgentService, remoteAgentService); - - // Native Host - const nativeHostService = new NativeHostService(this.configuration.windowId, mainProcessService) as INativeHostService; - serviceCollection.set(INativeHostService, nativeHostService); - - // Files - const fileService = this._register(new FileService(logService)); - serviceCollection.set(IFileService, fileService); - - this.registerFileSystemProviders(mainProcessService, sharedProcessWorkerWorkbenchService, fileService, logService, nativeHostService); - - // URI Identity - const uriIdentityService = new UriIdentityService(fileService); - serviceCollection.set(IUriIdentityService, uriIdentityService); - - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // - // NOTE: Please do NOT register services here. Use `registerSingleton()` - // from `workbench.common.main.ts` if the service is shared between - // desktop and web or `workbench.sandbox.main.ts` if the service - // is desktop only. - // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox - // - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - // Remote file system - this._register(RemoteFileSystemProvider.register(remoteAgentService, fileService, logService)); - - const payload = this.resolveWorkspaceInitializationPayload(environmentService); - - const [configurationService, storageService] = await Promise.all([ - this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { - - // Workspace - serviceCollection.set(IWorkspaceContextService, service); - - // Configuration - serviceCollection.set(IWorkbenchConfigurationService, service); - - return service; - }), - - this.createStorageService(payload, environmentService, mainProcessService).then(service => { - - // Storage - serviceCollection.set(IStorageService, service); - - return service; - }), - - this.createKeyboardLayoutService(mainProcessService).then(service => { - - // KeyboardLayout - serviceCollection.set(IKeyboardLayoutService, service); - - return service; - }) - ]); - - // Workspace Trust Service - const workspaceTrustEnablementService = new WorkspaceTrustEnablementService(configurationService, environmentService); - serviceCollection.set(IWorkspaceTrustEnablementService, workspaceTrustEnablementService); - - const workspaceTrustManagementService = new WorkspaceTrustManagementService(configurationService, remoteAuthorityResolverService, storageService, uriIdentityService, environmentService, configurationService, workspaceTrustEnablementService); - serviceCollection.set(IWorkspaceTrustManagementService, workspaceTrustManagementService); - - // Update workspace trust so that configuration is updated accordingly - configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkspaceTrusted()); - this._register(workspaceTrustManagementService.onDidChangeTrust(() => configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkspaceTrusted()))); - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // - // NOTE: Please do NOT register services here. Use `registerSingleton()` - // from `workbench.common.main.ts` if the service is shared between - // desktop and web or `workbench.sandbox.main.ts` if the service - // is desktop only. - // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox - // - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - - return { serviceCollection, logService, storageService }; - } - - private resolveWorkspaceInitializationPayload(environmentService: INativeWorkbenchEnvironmentService): IWorkspaceInitializationPayload { - let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined = this.configuration.workspace; - - // Fallback to empty workspace if we have no payload yet. - if (!workspaceInitializationPayload) { - let id: string; - if (this.configuration.backupPath) { - // we know the backupPath must be a unique path so we leverage its name as workspace ID - id = basename(this.configuration.backupPath); - } else if (environmentService.isExtensionDevelopment) { - // fallback to a reserved identifier when in extension development where backups are not stored - id = 'ext-dev'; - } else { - throw new Error('Unexpected window configuration without backupPath'); - } - - workspaceInitializationPayload = { id }; - } - - return workspaceInitializationPayload; - } - - private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { - const configurationCache = new ConfigurationCache([Schemas.file, Schemas.userData] /* Cache all non native resources */, environmentService, fileService); - const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); - - try { - await workspaceService.initialize(payload); - - return workspaceService; - } catch (error) { - onUnexpectedError(error); - - return workspaceService; - } - } - - private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, mainProcessService: IMainProcessService): Promise { - const storageService = new NativeStorageService(payload, mainProcessService, environmentService); - - try { - await storageService.initialize(); - - return storageService; - } catch (error) { - onUnexpectedError(error); - - return storageService; - } - } - - private async createKeyboardLayoutService(mainProcessService: IMainProcessService): Promise { - const keyboardLayoutService = new KeyboardLayoutService(mainProcessService); - - try { - await keyboardLayoutService.initialize(); - - return keyboardLayoutService; - } catch (error) { - onUnexpectedError(error); - - return keyboardLayoutService; - } - } -} diff --git a/src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts b/src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts deleted file mode 100644 index 4dafde8584371..0000000000000 --- a/src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { basename } from 'vs/base/common/path'; -import { isWindows } from 'vs/base/common/platform'; -import { localize } from 'vs/nls'; -import { FileDeleteOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { DiskFileSystemProvider as NodeDiskFileSystemProvider, IDiskFileSystemProviderOptions } from 'vs/platform/files/node/diskFileSystemProvider'; -import { ILogService } from 'vs/platform/log/common/log'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; - -export class DiskFileSystemProvider extends NodeDiskFileSystemProvider { - - constructor( - logService: ILogService, - private readonly nativeHostService: INativeHostService, - options?: IDiskFileSystemProviderOptions - ) { - super(logService, options); - } - - //#region Enable Trash capability as only extension to the node.js file provider - - override get capabilities(): FileSystemProviderCapabilities { - if (!this._capabilities) { - this._capabilities = super.capabilities | FileSystemProviderCapabilities.Trash; - } - - return this._capabilities; - } - - protected override async doDelete(filePath: string, opts: FileDeleteOptions): Promise { - if (!opts.useTrash) { - return super.doDelete(filePath, opts); - } - - try { - await this.nativeHostService.moveItemToTrash(filePath); - } catch (error) { - this.logService.error(error); - - throw new Error(isWindows ? localize('binFailed', "Failed to move '{0}' to the recycle bin", basename(filePath)) : localize('trashFailed', "Failed to move '{0}' to the trash", basename(filePath))); - } - } - - //#endregion -} diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 1db8caf392af5..e8430fcb4ab49 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -35,13 +35,6 @@ import 'vs/workbench/workbench.sandbox.main'; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -//#region --- workbench (desktop main) - -import 'vs/workbench/electron-browser/desktop.main'; - -//#endregion - - //#region --- workbench services diff --git a/src/vs/workbench/workbench.desktop.sandbox.main.ts b/src/vs/workbench/workbench.desktop.sandbox.main.ts index d7c7c3928ae2c..8ae202e8720dd 100644 --- a/src/vs/workbench/workbench.desktop.sandbox.main.ts +++ b/src/vs/workbench/workbench.desktop.sandbox.main.ts @@ -33,6 +33,14 @@ import 'vs/workbench/electron-sandbox/desktop.main'; //#region --- workbench services +import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +// TODO@bpasero sandbox: remove me when extension host is present +class SimpleExtensionService extends NullExtensionService { } + +registerSingleton(IExtensionService, SimpleExtensionService); + //#endregion diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index b24066e7a34c9..1b0eaa2d34e71 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -17,6 +17,13 @@ import 'vs/workbench/workbench.common.main'; //#endregion +//#region --- workbench (desktop main) + +import 'vs/workbench/electron-sandbox/desktop.main'; + +//#endregion + + //#region --- workbench parts import 'vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution'; From 8d61a33e38e4f5da5b7b511471dd353ebd9b3d46 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 10:48:05 +0100 Subject: [PATCH 0295/2210] editors - remove experimental `experimentalDisableClearInputOnSetInput` setting --- src/vs/workbench/browser/parts/editor/editor.ts | 3 +-- src/vs/workbench/browser/parts/editor/editorGroupView.ts | 2 +- src/vs/workbench/browser/parts/editor/editorPanes.ts | 7 ++----- src/vs/workbench/browser/workbench.contribution.ts | 5 ----- src/vs/workbench/common/editor.ts | 3 +-- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 1d41297143c0c..22791e7c00f1b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -39,8 +39,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { closeEmptyGroups: true, labelFormat: 'default', splitSizing: 'distribute', - splitOnDragAndDrop: true, - experimentalDisableClearInputOnSetInput: false //TODO@bpasero remove this setting in December + splitOnDragAndDrop: true }; export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 6f1fff939e1d0..d8eca61115fdb 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -211,7 +211,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.element.appendChild(this.editorContainer); // Editor pane - this.editorPane = this._register(this.scopedInstantiationService.createInstance(EditorPanes, this.editorContainer, this.accessor, this)); + this.editorPane = this._register(this.scopedInstantiationService.createInstance(EditorPanes, this.editorContainer, this)); this._onDidChange.input = this.editorPane.onDidChangeSizeConstraints; // Track Focus diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts index d12911518bbff..aab289adc8055 100644 --- a/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -13,7 +13,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; -import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Emitter } from 'vs/base/common/event'; import { assertIsDefined } from 'vs/base/common/types'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; @@ -84,7 +84,6 @@ export class EditorPanes extends Disposable { constructor( private parent: HTMLElement, - private accessor: IEditorGroupsAccessor, private groupView: IEditorGroupView, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -276,9 +275,7 @@ export class EditorPanes extends Disposable { // This ensures that a slow loading input will not // be visible for the duration of the new input to // load (https://github.com/microsoft/vscode/issues/34697) - if (this.accessor.partOptions.experimentalDisableClearInputOnSetInput !== true) { - editorPane.clearInput(); - } + editorPane.clearInput(); // Set the input to the editor pane await editorPane.setInput(editor, options, context, operation.token); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index dcb346a93dd19..556e8f5522d8e 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -241,11 +241,6 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': false, 'description': localize('perEditorGroup', "Controls if the limit of maximum opened editors should apply per editor group or across all editor groups.") }, - 'workbench.editor.experimentalDisableClearInputOnSetInput': { - 'type': 'boolean', - 'default': false, - 'description': localize('experimentalDisableClearInputOnSetInput', "Experimental setting: do not change unless instructed.") - }, 'workbench.commandPalette.history': { 'type': 'number', 'description': localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 0e1cadf75fa98..f9b267d7d89a6 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -864,8 +864,7 @@ interface IEditorPartConfiguration { decorations?: { badges?: boolean; colors?: boolean; - }, - experimentalDisableClearInputOnSetInput?: boolean; + } } export interface IEditorPartOptions extends IEditorPartConfiguration { From 16d0a319b28caa4b6cf4e6801fd508282b7533e0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 10:50:15 +0100 Subject: [PATCH 0296/2210] :sparkles: watcher - remove `vscode-nsfw` legacy watcher --- build/.moduleignore | 6 - build/gulpfile.reh.js | 6 +- package.json | 1 - remote/package.json | 1 - remote/yarn.lock | 12 - .../files/browser/htmlFileSystemProvider.ts | 1 - .../files/common/diskFileSystemProvider.ts | 22 +- src/vs/platform/files/common/fileService.ts | 12 +- src/vs/platform/files/common/files.ts | 3 +- .../files/common/ipcFileSystemProvider.ts | 6 +- src/vs/platform/files/common/watcher.ts | 64 +-- .../diskFileSystemProviderIpc.ts | 4 +- .../files/node/diskFileSystemProvider.ts | 41 +- .../{watcherService.ts => nodejsWatcher.ts} | 5 +- .../node/watcher/nsfw/nsfwWatcherService.ts | 478 ------------------ .../files/node/watcher/nsfw/watcher.ts | 39 -- .../files/node/watcher/nsfw/watcherService.ts | 74 --- ...rcelWatcherService.ts => parcelWatcher.ts} | 60 +-- ...tcherService.ts => parcelWatcherClient.ts} | 12 +- .../parcelWatcherMain.ts} | 4 +- .../files/node/watcher/parcel/watcherApp.ts | 12 - .../node/recursiveWatcher.integrationTest.ts | 248 ++++----- src/vs/platform/windows/common/windows.ts | 2 - .../electron-main/windowsMainService.ts | 1 - src/vs/server/remoteFileSystemProviderIpc.ts | 2 +- src/vs/workbench/buildfile.desktop.js | 3 +- .../files/browser/files.contribution.ts | 16 - .../contrib/files/browser/workspaceWatcher.ts | 4 +- .../browser/relauncher.contribution.ts | 8 - .../diskFileSystemProvider.ts | 10 +- ...tcherService.ts => parcelWatcherClient.ts} | 16 +- yarn.lock | 12 - 32 files changed, 239 insertions(+), 946 deletions(-) rename src/vs/platform/files/node/watcher/nodejs/{watcherService.ts => nodejsWatcher.ts} (98%) delete mode 100644 src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts delete mode 100644 src/vs/platform/files/node/watcher/nsfw/watcher.ts delete mode 100644 src/vs/platform/files/node/watcher/nsfw/watcherService.ts rename src/vs/platform/files/node/watcher/parcel/{parcelWatcherService.ts => parcelWatcher.ts} (90%) rename src/vs/platform/files/node/watcher/parcel/{watcherService.ts => parcelWatcherClient.ts} (77%) rename src/vs/platform/files/node/watcher/{nsfw/watcherApp.ts => parcel/parcelWatcherMain.ts} (81%) delete mode 100644 src/vs/platform/files/node/watcher/parcel/watcherApp.ts rename src/vs/workbench/services/files/electron-sandbox/{parcelWatcherService.ts => parcelWatcherClient.ts} (78%) diff --git a/build/.moduleignore b/build/.moduleignore index 233ca72a48a73..6efda5efd8d3b 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -78,12 +78,6 @@ node-pty/scripts/** !node-pty/build/Release/*.dll !node-pty/build/Release/*.node -vscode-nsfw/binding.gyp -vscode-nsfw/build/** -vscode-nsfw/src/** -vscode-nsfw/includes/** -!vscode-nsfw/build/Release/*.node - @parcel/watcher/binding.gyp @parcel/watcher/build/** @parcel/watcher/prebuilds/** diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 8be0864b171df..4fdfd725db416 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -103,11 +103,7 @@ const serverEntryPoints = [ exclude: ['vs/css', 'vs/nls'] }, { - name: 'vs/platform/files/node/watcher/nsfw/watcherApp', - exclude: ['vs/css', 'vs/nls'] - }, - { - name: 'vs/platform/files/node/watcher/parcel/watcherApp', + name: 'vs/platform/files/node/watcher/parcel/parcelWatcherMain', exclude: ['vs/css', 'vs/nls'] }, { diff --git a/package.json b/package.json index 6d1fb7cca0c87..f74bfda24a162 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,6 @@ "spdlog": "^0.13.0", "tas-client-umd": "0.1.4", "v8-inspect-profiler": "^0.0.22", - "vscode-nsfw": "2.1.8", "vscode-oniguruma": "1.6.1", "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", diff --git a/remote/package.json b/remote/package.json index 662ce3568ab72..7f0f62482783c 100644 --- a/remote/package.json +++ b/remote/package.json @@ -18,7 +18,6 @@ "node-pty": "0.11.0-beta11", "spdlog": "^0.13.0", "tas-client-umd": "0.1.4", - "vscode-nsfw": "2.1.8", "vscode-oniguruma": "1.6.1", "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index 959b0f97cd592..58db506c0ea54 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -384,11 +384,6 @@ node-addon-api@^3.2.1: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== -node-addon-api@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" - integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== - node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" @@ -472,13 +467,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -vscode-nsfw@2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-2.1.8.tgz#88f5e56b22b2fd0be582e73eb1158ea8257f6c6c" - integrity sha512-tFnxPIuM65czw/Kjz8KXD88fIJtnCjzQ0ighS0a1yasVv6jKkANAlGffiOitTLMkDjvFCY8OyP6xjarTkpu/VQ== - dependencies: - node-addon-api "^4.2.0" - vscode-oniguruma@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" diff --git a/src/vs/platform/files/browser/htmlFileSystemProvider.ts b/src/vs/platform/files/browser/htmlFileSystemProvider.ts index 4d42c21fe1594..3acc9fa23159c 100644 --- a/src/vs/platform/files/browser/htmlFileSystemProvider.ts +++ b/src/vs/platform/files/browser/htmlFileSystemProvider.ts @@ -22,7 +22,6 @@ export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWr readonly onDidChangeCapabilities = Event.None; readonly onDidChangeFile = Event.None; - readonly onDidErrorOccur = Event.None; //#endregion diff --git a/src/vs/platform/files/common/diskFileSystemProvider.ts b/src/vs/platform/files/common/diskFileSystemProvider.ts index 2e2fec710c864..78e62c21665a9 100644 --- a/src/vs/platform/files/common/diskFileSystemProvider.ts +++ b/src/vs/platform/files/common/diskFileSystemProvider.ts @@ -11,7 +11,7 @@ import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/ba import { normalize } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { IFileChange, IWatchOptions } from 'vs/platform/files/common/files'; -import { IDiskFileChange, ILogMessage, IWatchRequest, toFileChanges, WatcherService } from 'vs/platform/files/common/watcher'; +import { AbstractRecursiveWatcherClient, IDiskFileChange, ILogMessage, IWatchRequest, toFileChanges } from 'vs/platform/files/common/watcher'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; export abstract class AbstractDiskFileSystemProvider extends Disposable { @@ -24,13 +24,13 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable { //#region File Watching - protected readonly _onDidErrorOccur = this._register(new Emitter()); - readonly onDidErrorOccur = this._onDidErrorOccur.event; - protected readonly _onDidChangeFile = this._register(new Emitter()); readonly onDidChangeFile = this._onDidChangeFile.event; - private recursiveWatcher: WatcherService | undefined; + protected readonly _onDidWatchError = this._register(new Emitter()); + readonly onDidWatchError = this._onDidWatchError.event; + + private recursiveWatcher: AbstractRecursiveWatcherClient | undefined; private readonly recursiveFoldersToWatch: IWatchRequest[] = []; private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer(0)); @@ -90,7 +90,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable { return this.doWatch(this.recursiveWatcher, this.recursiveFoldersToWatch); } - protected doWatch(watcher: WatcherService, requests: IWatchRequest[]): Promise { + protected doWatch(watcher: AbstractRecursiveWatcherClient, requests: IWatchRequest[]): Promise { return watcher.watch(requests); } @@ -98,10 +98,10 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable { onChange: (changes: IDiskFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean - ): WatcherService; + ): AbstractRecursiveWatcherClient; private watchNonRecursive(resource: URI): IDisposable { - const watcherService = this.createNonRecursiveWatcher( + const watcher = this.createNonRecursiveWatcher( this.toFilePath(resource), changes => this._onDidChangeFile.fire(toFileChanges(changes)), msg => this.onWatcherLogMessage(msg), @@ -109,15 +109,15 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable { ); const logLevelListener = this.logService.onDidChangeLogLevel(() => { - watcherService.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); + watcher.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); }); - return combinedDisposable(watcherService, logLevelListener); + return combinedDisposable(watcher, logLevelListener); } private onWatcherLogMessage(msg: ILogMessage): void { if (msg.type === 'error') { - this._onDidErrorOccur.fire(msg.message); + this._onDidWatchError.fire(msg.message); } this.logService[msg.type](msg.message); diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 968c9968ec7ad..21799596649f5 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -62,8 +62,8 @@ export class FileService extends Disposable implements IFileService { const providerDisposables = new DisposableStore(); providerDisposables.add(provider.onDidChangeFile(changes => this.onDidChangeFile(changes, this.isPathCaseSensitive(provider)))); providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider, scheme }))); - if (typeof provider.onDidErrorOccur === 'function') { - providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error)))); + if (typeof provider.onDidWatchError === 'function') { + providerDisposables.add(provider.onDidWatchError(error => this._onDidWatchError.fire(new Error(error)))); } return toDisposable(() => { @@ -166,11 +166,12 @@ export class FileService extends Disposable implements IFileService { //#endregion + //#region Operation events + private readonly _onDidRunOperation = this._register(new Emitter()); readonly onDidRunOperation = this._onDidRunOperation.event; - private readonly _onError = this._register(new Emitter()); - readonly onError = this._onError.event; + //#endregion //#region File Metadata Resolving @@ -993,6 +994,9 @@ export class FileService extends Disposable implements IFileService { private readonly _onDidFilesChange = this._register(new Emitter()); readonly onDidFilesChange = this._onDidFilesChange.event; + private readonly _onDidWatchError = this._register(new Emitter()); + readonly onDidWatchError = this._onDidWatchError.event; + private readonly _onDidChangeFilesRaw = this._register(new Emitter()); readonly onDidChangeFilesRaw = this._onDidChangeFilesRaw.event; diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 9e4024042feb8..fa6ce1b497e22 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -435,9 +435,8 @@ export interface IFileSystemProvider { readonly capabilities: FileSystemProviderCapabilities; readonly onDidChangeCapabilities: Event; - readonly onDidErrorOccur?: Event; - readonly onDidChangeFile: Event; + readonly onDidWatchError?: Event; watch(resource: URI, opts: IWatchOptions): IDisposable; stat(resource: URI): Promise; diff --git a/src/vs/platform/files/common/ipcFileSystemProvider.ts b/src/vs/platform/files/common/ipcFileSystemProvider.ts index c85d243674518..d3fa6be32a156 100644 --- a/src/vs/platform/files/common/ipcFileSystemProvider.ts +++ b/src/vs/platform/files/common/ipcFileSystemProvider.ts @@ -186,8 +186,8 @@ export class IPCFileSystemProvider extends Disposable implements private readonly _onDidChange = this._register(new Emitter()); readonly onDidChangeFile = this._onDidChange.event; - private readonly _onDidErrorOccur = this._register(new Emitter()); - readonly onDidErrorOccur = this._onDidErrorOccur.event; + private readonly _onDidWatchError = this._register(new Emitter()); + readonly onDidWatchError = this._onDidWatchError.event; // The contract for file watching via remote is to identify us // via a unique but readonly session ID. Since the remote is @@ -208,7 +208,7 @@ export class IPCFileSystemProvider extends Disposable implements this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type }))); } else { const error = eventsOrError; - this._onDidErrorOccur.fire(error); + this._onDidWatchError.fire(error); } })); } diff --git a/src/vs/platform/files/common/watcher.ts b/src/vs/platform/files/common/watcher.ts index eb1d60df6360c..589fea209d123 100644 --- a/src/vs/platform/files/common/watcher.ts +++ b/src/vs/platform/files/common/watcher.ts @@ -9,7 +9,7 @@ import { isLinux } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; import { FileChangeType, IFileChange, isParent } from 'vs/platform/files/common/files'; -export interface IWatcherService { +export interface IRecursiveWatcher { /** * A normalized file change event from the raw events @@ -25,15 +25,15 @@ export interface IWatcherService { /** * An event to indicate an error occured from the watcher * that is unrecoverable. Listeners should restart the - * service if possible. + * watcher if possible. */ readonly onDidError: Event; /** - * Configures the watcher service to watch according - * to the requests. Any existing watched path that - * is not in the array, will be removed from watching - * and any new path will be added to watching. + * Configures the watcher to watch according to the + * requests. Any existing watched path that is not + * in the array, will be removed from watching and + * any new path will be added to watching. */ watch(requests: IWatchRequest[]): Promise; @@ -48,12 +48,12 @@ export interface IWatcherService { stop(): Promise; } -export abstract class AbstractWatcherService extends Disposable { +export abstract class AbstractRecursiveWatcherClient extends Disposable { private static readonly MAX_RESTARTS = 5; - private service: IWatcherService | undefined; - private readonly serviceDisposables = this._register(new MutableDisposable()); + private watcher: IRecursiveWatcher | undefined; + private readonly watcherDisposables = this._register(new MutableDisposable()); private requests: IWatchRequest[] | undefined = undefined; @@ -67,28 +67,28 @@ export abstract class AbstractWatcherService extends Disposable { super(); } - protected abstract createService(disposables: DisposableStore): IWatcherService; + protected abstract createWatcher(disposables: DisposableStore): IRecursiveWatcher; protected init(): void { - // Associate disposables to the service + // Associate disposables to the watcher const disposables = new DisposableStore(); - this.serviceDisposables.value = disposables; + this.watcherDisposables.value = disposables; - // Ask implementors to create the service - this.service = this.createService(disposables); - this.service.setVerboseLogging(this.verboseLogging); + // Ask implementors to create the watcher + this.watcher = this.createWatcher(disposables); + this.watcher.setVerboseLogging(this.verboseLogging); // Wire in event handlers - disposables.add(this.service.onDidChangeFile(e => this.onFileChanges(e))); - disposables.add(this.service.onDidLogMessage(e => this.onLogMessage(e))); - disposables.add(this.service.onDidError(e => this.onError(e))); + disposables.add(this.watcher.onDidChangeFile(e => this.onFileChanges(e))); + disposables.add(this.watcher.onDidLogMessage(e => this.onLogMessage(e))); + disposables.add(this.watcher.onDidError(e => this.onError(e))); } protected onError(error: string): void { // Restart up to N times - if (this.restartCounter < AbstractWatcherService.MAX_RESTARTS && this.requests) { + if (this.restartCounter < AbstractRecursiveWatcherClient.MAX_RESTARTS && this.requests) { this.error(`restarting watcher after error: ${error}`); this.restart(this.requests); } @@ -109,13 +109,13 @@ export abstract class AbstractWatcherService extends Disposable { async watch(requests: IWatchRequest[]): Promise { this.requests = requests; - await this.service?.watch(requests); + await this.watcher?.watch(requests); } async setVerboseLogging(verboseLogging: boolean): Promise { this.verboseLogging = verboseLogging; - await this.service?.setVerboseLogging(verboseLogging); + await this.watcher?.setVerboseLogging(verboseLogging); } private error(message: string) { @@ -124,31 +124,13 @@ export abstract class AbstractWatcherService extends Disposable { override dispose(): void { - // Render the serve invalid from here - this.service = undefined; + // Render the watcher invalid from here + this.watcher = undefined; return super.dispose(); } } -/** - * Base class of any watcher service we support. - * - * TODO@bpasero delete and replace with `AbstractWatcherService` - */ -export abstract class WatcherService extends Disposable { - - /** - * Asks to watch the provided folders. - */ - abstract watch(requests: IWatchRequest[]): Promise; - - /** - * Enable verbose logging from the watcher. - */ - abstract setVerboseLogging(verboseLogging: boolean): Promise; -} - export interface IWatchRequest { /** diff --git a/src/vs/platform/files/electron-main/diskFileSystemProviderIpc.ts b/src/vs/platform/files/electron-main/diskFileSystemProviderIpc.ts index 09e45e3d8735e..0fd5948a7e595 100644 --- a/src/vs/platform/files/electron-main/diskFileSystemProviderIpc.ts +++ b/src/vs/platform/files/electron-main/diskFileSystemProviderIpc.ts @@ -9,7 +9,7 @@ import { isWindows } from 'vs/base/common/platform'; import { Emitter } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { FileDeleteOptions, IFileChange, IWatchOptions, createFileSystemProviderError, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; -import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService'; +import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { basename, normalize } from 'vs/base/common/path'; import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -88,7 +88,7 @@ class SessionFileWatcher extends Disposable implements ISessionFileWatcher { this.watcherRequests.set(req, disposable); disposable.add(toDisposable(() => this.watcherRequests.delete(req))); - const watcher = disposable.add(new NodeJSWatcherService( + const watcher = disposable.add(new NodeJSFileWatcher( normalize(resource.fsPath), changes => this.sessionEmitter.fire(toFileChanges(changes)), msg => this.onWatcherLogMessage(msg), diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 26d03507a60c0..d320a91f6ebcd 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -20,10 +20,9 @@ import { IDirent, Promises, RimRafMode, SymlinkSupport } from 'vs/base/node/pfs' import { localize } from 'vs/nls'; import { createFileSystemProviderError, FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat } from 'vs/platform/files/common/files'; import { readFileIntoStream } from 'vs/platform/files/common/io'; -import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService'; -import { FileWatcher as NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcherService'; -import { FileWatcher as ParcelWatcherService } from 'vs/platform/files/node/watcher/parcel/watcherService'; -import { IDiskFileChange, ILogMessage, IWatchRequest, WatcherService } from 'vs/platform/files/common/watcher'; +import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; +import { ParcelWatcherClient } from 'vs/platform/files/node/watcher/parcel/parcelWatcherClient'; +import { AbstractRecursiveWatcherClient, IDiskFileChange, ILogMessage, IWatchRequest } from 'vs/platform/files/common/watcher'; import { ILogService } from 'vs/platform/log/common/log'; import { AbstractDiskFileSystemProvider } from 'vs/platform/files/common/diskFileSystemProvider'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -63,7 +62,6 @@ export interface IWatcherOptions { export interface IDiskFileSystemProviderOptions { watcher?: IWatcherOptions; - legacyWatcher?: string; } export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider implements @@ -551,38 +549,15 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple onChange: (changes: IDiskFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean - ): WatcherService { - let watcherImpl: { - new( - onChange: (changes: IDiskFileChange[]) => void, - onLogMessage: (msg: ILogMessage) => void, - verboseLogging: boolean, - watcherOptions?: IWatcherOptions - ): WatcherService - }; - - let enableLegacyWatcher = false; - if (this.options?.watcher?.usePolling) { - enableLegacyWatcher = false; // must use Parcel watcher for when polling is required - } else { - enableLegacyWatcher = this.options?.legacyWatcher === 'on'; // setting always wins - } - - if (enableLegacyWatcher) { - watcherImpl = NsfwWatcherService; - } else { - watcherImpl = ParcelWatcherService; - } - - return new watcherImpl( + ): AbstractRecursiveWatcherClient { + return new ParcelWatcherClient( changes => onChange(changes), msg => onLogMessage(msg), - verboseLogging, - this.options?.watcher + verboseLogging ); } - protected override doWatch(watcher: WatcherService, requests: IWatchRequest[]): Promise { + protected override doWatch(watcher: AbstractRecursiveWatcherClient, requests: IWatchRequest[]): Promise { const usePolling = this.options?.watcher?.usePolling; if (usePolling === true) { for (const request of requests) { @@ -605,7 +580,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ): IDisposable & { setVerboseLogging: (verboseLogging: boolean) => void } { - return new NodeJSWatcherService( + return new NodeJSFileWatcher( path, changes => onChange(changes), msg => onLogMessage(msg), diff --git a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts similarity index 98% rename from src/vs/platform/files/node/watcher/nodejs/watcherService.ts rename to src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts index 983b291e22991..6ea4e08eb93e2 100644 --- a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts @@ -12,11 +12,12 @@ import { CHANGE_BUFFER_DELAY, watchFile, watchFolder } from 'vs/base/node/watche import { FileChangeType } from 'vs/platform/files/common/files'; import { IDiskFileChange, ILogMessage, coalesceEvents } from 'vs/platform/files/common/watcher'; -export class FileWatcher extends Disposable { - private isDisposed: boolean | undefined; +export class NodeJSFileWatcher extends Disposable { private readonly fileChangesDelayer: ThrottledDelayer = this._register(new ThrottledDelayer(CHANGE_BUFFER_DELAY * 2 /* sync on delay from underlying library */)); private fileChangesBuffer: IDiskFileChange[] = []; + + private isDisposed: boolean | undefined; constructor( private path: string, diff --git a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts deleted file mode 100644 index 7b415d8b90258..0000000000000 --- a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts +++ /dev/null @@ -1,478 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nsfw from 'vscode-nsfw'; -import { existsSync } from 'fs'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { Emitter } from 'vs/base/common/event'; -import { parse, ParsedPattern } from 'vs/base/common/glob'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; -import { normalizeNFC } from 'vs/base/common/normalization'; -import { dirname, join } from 'vs/base/common/path'; -import { isMacintosh } from 'vs/base/common/platform'; -import { realcaseSync, realpathSync } from 'vs/base/node/extpath'; -import { FileChangeType } from 'vs/platform/files/common/files'; -import { IWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcher'; -import { IDiskFileChange, ILogMessage, coalesceEvents, IWatchRequest } from 'vs/platform/files/common/watcher'; -import { watchFolder } from 'vs/base/node/watcher'; - -interface IWatcher extends IDisposable { - - /** - * The NSFW instance is resolved when the watching has started. - */ - readonly instance: Promise; - - /** - * The watch request associated to the watcher. - */ - request: IWatchRequest; - - /** - * Associated ignored patterns for the watcher that can be updated. - */ - ignored: ParsedPattern[]; - - /** - * How often this watcher has been restarted in case of an unexpected - * shutdown. - */ - restarts: number; - - /** - * The cancellation token associated with the lifecycle of the watcher. - */ - token: CancellationToken; -} - -export class NsfwWatcherService extends Disposable implements IWatcherService { - - private static readonly MAX_RESTARTS = 5; // number of restarts we allow before giving up in case of unexpected shutdown - - private static readonly MAP_NSFW_ACTION_TO_FILE_CHANGE = new Map( - [ - [nsfw.actions.CREATED, FileChangeType.ADDED], - [nsfw.actions.MODIFIED, FileChangeType.UPDATED], - [nsfw.actions.DELETED, FileChangeType.DELETED], - ] - ); - - private readonly _onDidChangeFile = this._register(new Emitter()); - readonly onDidChangeFile = this._onDidChangeFile.event; - - private readonly _onDidLogMessage = this._register(new Emitter()); - readonly onDidLogMessage = this._onDidLogMessage.event; - - protected readonly watchers = new Map(); - - private verboseLogging = false; - private enospcErrorLogged = false; - - constructor() { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Error handling on process - process.on('uncaughtException', error => this.onError(error)); - process.on('unhandledRejection', error => this.onError(error)); - } - - async watch(requests: IWatchRequest[]): Promise { - - // Figure out duplicates to remove from the requests - const normalizedRequests = this.normalizeRequests(requests); - - // Gather paths that we should start watching - const requestsToStartWatching = normalizedRequests.filter(request => { - return !this.watchers.has(request.path); - }); - - // Gather paths that we should stop watching - const pathsToStopWatching = Array.from(this.watchers.keys()).filter(watchedPath => { - return !normalizedRequests.find(normalizedRequest => normalizedRequest.path === watchedPath); - }); - - // Logging - this.debug(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes})`).join(',')}`); - this.debug(`Request to stop watching: ${pathsToStopWatching.join(',')}`); - - // Stop watching as instructed - for (const pathToStopWatching of pathsToStopWatching) { - this.stopWatching(pathToStopWatching); - } - - // Start watching as instructed - for (const request of requestsToStartWatching) { - this.startWatching(request); - } - - // Update ignore rules for all watchers - for (const request of normalizedRequests) { - const watcher = this.watchers.get(request.path); - if (watcher) { - watcher.request = request; - watcher.ignored = this.toExcludePatterns(request.excludes); - } - } - } - - private toExcludePatterns(excludes: string[] | undefined): ParsedPattern[] { - return Array.isArray(excludes) ? excludes.map(exclude => parse(exclude)) : []; - } - - private startWatching(request: IWatchRequest, restarts = 0): void { - const cts = new CancellationTokenSource(); - - let nsfwPromiseResolve: (watcher: nsfw.NSFW) => void; - const instance = new Promise(resolve => nsfwPromiseResolve = resolve); - - // Remember as watcher instance - const watcher: IWatcher = { - request, - instance, - ignored: this.toExcludePatterns(request.excludes), - restarts, - token: cts.token, - dispose: () => { - cts.dispose(true); - instance.then(instance => instance.stop()); - } - }; - this.watchers.set(request.path, watcher); - - // Path checks for symbolic links / wrong casing - const { realBasePathDiffers, realBasePathLength } = this.checkRequest(request); - - let undeliveredFileEvents: IDiskFileChange[] = []; - - const onRawFileEvent = (path: string, type: FileChangeType) => { - if (!this.isPathIgnored(path, watcher.ignored)) { - undeliveredFileEvents.push({ type, path }); - } else if (this.verboseLogging) { - this.log(` >> ignored ${path}`); - } - }; - - nsfw(request.path, events => { - if (watcher.token.isCancellationRequested) { - return; // return early when disposed - } - - for (const event of events) { - - // Log the raw event before normalization or checking for ignore patterns - if (this.verboseLogging) { - const logPath = event.action === nsfw.actions.RENAMED ? `${join(event.directory, event.oldFile || '')} -> ${event.newFile}` : join(event.directory, event.file || ''); - this.log(`${event.action === nsfw.actions.CREATED ? '[ADDED]' : event.action === nsfw.actions.DELETED ? '[DELETED]' : event.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]'} ${logPath}`); - } - - // Rename: convert into DELETE & ADD - if (event.action === nsfw.actions.RENAMED) { - onRawFileEvent(join(event.directory, event.oldFile || ''), FileChangeType.DELETED); // Rename fires when a file's name changes within a single directory - onRawFileEvent(join(event.newDirectory || event.directory, event.newFile || ''), FileChangeType.ADDED); - } - - // Created, modified, deleted: take as is - else { - onRawFileEvent(join(event.directory, event.file || ''), NsfwWatcherService.MAP_NSFW_ACTION_TO_FILE_CHANGE.get(event.action)!); - } - } - - // Reset undelivered events array - const undeliveredFileEventsToEmit = undeliveredFileEvents; - undeliveredFileEvents = []; - - // Broadcast to clients normalized - const normalizedEvents = coalesceEvents(this.normalizeEvents(undeliveredFileEventsToEmit, request, realBasePathDiffers, realBasePathLength)); - this.emitEvents(normalizedEvents); - }, this.getOptions(watcher)).then(async nsfwWatcher => { - - // Begin watching unless disposed already - if (!watcher.token.isCancellationRequested) { - await nsfwWatcher.start(); - } - - return nsfwWatcher; - }).then(nsfwWatcher => { - this.debug(`Started watching: ${request.path}`); - - nsfwPromiseResolve(nsfwWatcher); - }); - } - - protected getOptions(watcher: IWatcher): nsfw.Options { - return { - - // We must install an error callback, otherwise any error - // that is thrown from the watcher will result in process exit - errorCallback: error => { - if (!watcher.token.isCancellationRequested) { - this.onError(error, watcher); // error handling only if we are not disposed yet - } - }, - - // The default delay of NSFW is 500 but we want to - // react a bit faster than that. - debounceMS: 250 - }; - } - - private emitEvents(events: IDiskFileChange[]): void { - - // Send outside - this._onDidChangeFile.fire(events); - - // Logging - if (this.verboseLogging) { - for (const event of events) { - this.log(` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); - } - } - } - - private checkRequest(request: IWatchRequest): { realBasePathDiffers: boolean, realBasePathLength: number } { - let realBasePathDiffers = false; - let realBasePathLength = request.path.length; - - // macOS: nsfw will report paths in their dereferenced and real casing - // form, so we need to detect this early on to be able to rewrite the - // file events to the original requested form. - // Note: Other platforms do not seem to have these path issues. - if (isMacintosh) { - try { - - // First check for symbolic link - let realBasePath = realpathSync(request.path); - - // Second check for casing difference - if (request.path === realBasePath) { - realBasePath = (realcaseSync(request.path) || request.path); - } - - if (request.path !== realBasePath) { - realBasePathLength = realBasePath.length; - realBasePathDiffers = true; - - this.warn(`correcting a path to watch that seems to be a symbolic link (original: ${request.path}, real: ${realBasePath})`); - } - } catch (error) { - // ignore - } - } - - return { realBasePathDiffers, realBasePathLength }; - } - - private normalizeEvents(events: IDiskFileChange[], request: IWatchRequest, realBasePathDiffers: boolean, realBasePathLength: number): IDiskFileChange[] { - if (isMacintosh) { - for (const event of events) { - - // Mac uses NFD unicode form on disk, but we want NFC - event.path = normalizeNFC(event.path); - - // Convert paths back to original form in case it differs - if (realBasePathDiffers) { - event.path = request.path + event.path.substr(realBasePathLength); - } - } - } - - return events; - } - - private onError(error: unknown, watcher?: IWatcher): void { - const msg = toErrorMessage(error); - - // Specially handle ENOSPC errors that can happen when - // the watcher consumes so many file descriptors that - // we are running into a limit. We only want to warn - // once in this case to avoid log spam. - // See https://github.com/microsoft/vscode/issues/7950 - if (msg.indexOf('Inotify limit reached') !== -1) { - if (!this.enospcErrorLogged) { - this.error('Inotify limit reached (ENOSPC)', watcher); - - this.enospcErrorLogged = true; - } - } - - // Any other error is unexpected and we should try to - // restart the watcher as a result to get into healthy - // state again. - else { - const handled = this.onUnexpectedError(msg, watcher); - if (!handled) { - this.error(`Unexpected error: ${msg} (EUNKNOWN)`, watcher); - } - } - } - - private onUnexpectedError(error: string, watcher?: IWatcher): boolean { - if (!watcher || watcher.restarts >= NsfwWatcherService.MAX_RESTARTS) { - return false; // we need a watcher that has not been restarted MAX_RESTARTS times already - } - - let handled = false; - - // Just try to restart watcher now if the path still exists - if (existsSync(watcher.request.path)) { - this.warn(`Watcher will be restarted due to unexpected error: ${error}`, watcher); - this.restartWatching(watcher); - - handled = true; - } - - // Otherwise try to monitor the path coming back before - // restarting the watcher - else { - handled = this.onWatchedPathDeleted(watcher); - } - - return handled; - } - - private onWatchedPathDeleted(watcher: IWatcher): boolean { - this.warn('Watcher shutdown because watched path got deleted', watcher); - - // Send a manual event given we know the root got deleted - this.emitEvents([{ path: watcher.request.path, type: FileChangeType.DELETED }]); - - const parentPath = dirname(watcher.request.path); - if (existsSync(parentPath)) { - const disposable = watchFolder(parentPath, (type, path) => { - if (watcher.token.isCancellationRequested) { - return; // return early when disposed - } - - // Watcher path came back! Restart watching... - if (path === watcher.request.path && (type === 'added' || type === 'changed')) { - this.warn('Watcher restarts because watched path got created again', watcher); - - // Stop watching that parent folder - disposable.dispose(); - - // Send a manual event given we know the root got added again - this.emitEvents([{ path: watcher.request.path, type: FileChangeType.ADDED }]); - - // Restart the file watching delayed - this.restartWatching(watcher); - } - }, error => { - // Ignore - }); - - // Make sure to stop watching when the watcher is disposed - watcher.token.onCancellationRequested(() => disposable.dispose()); - - return true; // handled - } - - return false; // not handled - } - - async stop(): Promise { - for (const [path] of this.watchers) { - this.stopWatching(path); - } - - this.watchers.clear(); - } - - private restartWatching(watcher: IWatcher, delay = 800): void { - - // Restart watcher delayed to accomodate for - // changes on disk that have triggered the - // need for a restart in the first place. - const scheduler = new RunOnceScheduler(() => { - if (watcher.token.isCancellationRequested) { - return; // return early when disposed - } - - // Stop/start watcher counting the restarts - this.stopWatching(watcher.request.path); - this.startWatching(watcher.request, watcher.restarts + 1); - }, delay); - scheduler.schedule(); - watcher.token.onCancellationRequested(() => scheduler.dispose()); - } - - private stopWatching(path: string): void { - const watcher = this.watchers.get(path); - if (watcher) { - watcher.dispose(); - this.watchers.delete(path); - } - } - - protected normalizeRequests(requests: IWatchRequest[]): IWatchRequest[] { - const requestTrie = TernarySearchTree.forPaths(); - - // Sort requests by path length to have shortest first - // to have a way to prevent children to be watched if - // parents exist. - requests.sort((requestA, requestB) => requestA.path.length - requestB.path.length); - - // Only consider requests for watching that are not - // a child of an existing request path to prevent - // duplication. - // - // However, allow explicit requests to watch folders - // that are symbolic links because the NSFW watcher - // does not allow to recursively watch symbolic links. - for (const request of requests) { - if (requestTrie.findSubstr(request.path)) { - try { - const realpath = realpathSync(request.path); - if (realpath === request.path) { - this.warn(`ignoring a path for watching who's parent is already watched: ${request.path}`); - - continue; // path is not a symbolic link or similar - } - } catch (error) { - continue; // invalid path - ignore from watching - } - } - - requestTrie.set(request.path, request); - } - - return Array.from(requestTrie).map(([, request]) => request); - } - - private isPathIgnored(absolutePath: string, ignored: ParsedPattern[]): boolean { - return ignored.some(ignore => ignore(absolutePath)); - } - - async setVerboseLogging(enabled: boolean): Promise { - this.verboseLogging = enabled; - } - - private log(message: string) { - this._onDidLogMessage.fire({ type: 'trace', message: this.toMessage(message) }); - } - - private warn(message: string, watcher?: IWatcher) { - this._onDidLogMessage.fire({ type: 'warn', message: this.toMessage(message, watcher) }); - } - - private error(message: string, watcher: IWatcher | undefined) { - this._onDidLogMessage.fire({ type: 'error', message: this.toMessage(message, watcher) }); - } - - private debug(message: string): void { - this._onDidLogMessage.fire({ type: 'debug', message: this.toMessage(message) }); - } - - private toMessage(message: string, watcher?: IWatcher): string { - return watcher ? `[File Watcher (nsfw)] ${message} (path: ${watcher.request.path})` : `[File Watcher (nsfw)] ${message}`; - } -} diff --git a/src/vs/platform/files/node/watcher/nsfw/watcher.ts b/src/vs/platform/files/node/watcher/nsfw/watcher.ts deleted file mode 100644 index 16e88490ff9a9..0000000000000 --- a/src/vs/platform/files/node/watcher/nsfw/watcher.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IDiskFileChange, ILogMessage, IWatchRequest } from 'vs/platform/files/common/watcher'; - -export interface IWatcherService { - - /** - * A normalized file change event from the raw events - * the watcher emits. - */ - readonly onDidChangeFile: Event; - - /** - * An event to indicate a message that should get logged. - */ - readonly onDidLogMessage: Event; - - /** - * Configures the watcher service to watch according - * to the requests. Any existing watched path that - * is not in the array, will be removed from watching - * and any new path will be added to watching. - */ - watch(requests: IWatchRequest[]): Promise; - - /** - * Enable verbose logging in the watcher. - */ - setVerboseLogging(enabled: boolean): Promise; - - /** - * Stop all watchers. - */ - stop(): Promise; -} diff --git a/src/vs/platform/files/node/watcher/nsfw/watcherService.ts b/src/vs/platform/files/node/watcher/nsfw/watcherService.ts deleted file mode 100644 index ec19ca7b2cf76..0000000000000 --- a/src/vs/platform/files/node/watcher/nsfw/watcherService.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { FileAccess } from 'vs/base/common/network'; -import { getNextTickChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { IWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcher'; -import { IDiskFileChange, ILogMessage, IWatchRequest, WatcherService } from 'vs/platform/files/common/watcher'; - -export class FileWatcher extends WatcherService { - - private service: IWatcherService | undefined; - - private isDisposed = false; - - constructor( - private readonly onDidFilesChange: (changes: IDiskFileChange[]) => void, - private readonly onLogMessage: (msg: ILogMessage) => void, - private verboseLogging: boolean - ) { - super(); - - this.startWatching(); - } - - private startWatching(): void { - const client = this._register(new Client( - FileAccess.asFileUri('bootstrap-fork', require).fsPath, - { - serverName: 'File Watcher (nsfw)', - args: ['--type=watcherServiceNSFW'], - env: { - VSCODE_AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/nsfw/watcherApp', - VSCODE_PIPE_LOGGING: 'true', - VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client - } - } - )); - - // Initialize watcher - this.service = ProxyChannel.toService(getNextTickChannel(client.getChannel('watcher'))); - this.service.setVerboseLogging(this.verboseLogging); - - // Wire in event handlers - this._register(this.service.onDidChangeFile(e => !this.isDisposed && this.onDidFilesChange(e))); - this._register(this.service.onDidLogMessage(e => this.onLogMessage(e))); - } - - async setVerboseLogging(verboseLogging: boolean): Promise { - this.verboseLogging = verboseLogging; - - if (!this.isDisposed) { - await this.service?.setVerboseLogging(verboseLogging); - } - } - - error(message: string) { - this.onLogMessage({ type: 'error', message: `[File Watcher (nsfw)] ${message}` }); - } - - async watch(requests: IWatchRequest[]): Promise { - if (!this.isDisposed) { - await this.service?.watch(requests); - } - } - - override dispose(): void { - this.isDisposed = true; - - super.dispose(); - } -} diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts similarity index 90% rename from src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts rename to src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index 26963631571d4..7fe7d63be31fa 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -22,9 +22,9 @@ import { generateUuid } from 'vs/base/common/uuid'; import { realcaseSync, realpathSync } from 'vs/base/node/extpath'; import { watchFolder } from 'vs/base/node/watcher'; import { FileChangeType } from 'vs/platform/files/common/files'; -import { IDiskFileChange, ILogMessage, coalesceEvents, IWatchRequest, IWatcherService } from 'vs/platform/files/common/watcher'; +import { IDiskFileChange, ILogMessage, coalesceEvents, IWatchRequest, IRecursiveWatcher } from 'vs/platform/files/common/watcher'; -export interface IWatcher { +export interface IParcelWatcherInstance { /** * Signals when the watcher is ready to watch. @@ -54,7 +54,7 @@ export interface IWatcher { stop(): Promise; } -export class ParcelWatcherService extends Disposable implements IWatcherService { +export class ParcelWatcher extends Disposable implements IRecursiveWatcher { private static readonly MAP_PARCEL_WATCHER_ACTION_TO_FILE_CHANGE = new Map( [ @@ -87,7 +87,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService private readonly _onDidError = this._register(new Emitter()); readonly onDidError = this._onDidError.event; - protected readonly watchers = new Map(); + protected readonly watchers = new Map(); private verboseLogging = false; private enospcErrorLogged = false; @@ -163,7 +163,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // level of the file watcher as much as possible. // Refs: https://github.com/parcel-bundler/watcher/issues/64 for (const exclude of excludes) { - const isGlob = exclude.includes(ParcelWatcherService.GLOB_MARKERS.Star); + const isGlob = exclude.includes(ParcelWatcher.GLOB_MARKERS.Star); // Glob pattern: check for typical patterns and convert let normalizedExclude: string | undefined = undefined; @@ -171,9 +171,9 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // Examples: **, **/**, **\** if ( - exclude === ParcelWatcherService.GLOB_MARKERS.GlobStar || - exclude === ParcelWatcherService.GLOB_MARKERS.GlobStarPosix || - exclude === ParcelWatcherService.GLOB_MARKERS.GlobStarWindows + exclude === ParcelWatcher.GLOB_MARKERS.GlobStar || + exclude === ParcelWatcher.GLOB_MARKERS.GlobStarPosix || + exclude === ParcelWatcher.GLOB_MARKERS.GlobStarWindows ) { normalizedExclude = path; } @@ -184,15 +184,15 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // - **/build-folder // - output/** else { - const startsWithGlobStar = exclude.startsWith(ParcelWatcherService.GLOB_MARKERS.GlobStarPathStartPosix) || exclude.startsWith(ParcelWatcherService.GLOB_MARKERS.GlobStarPathStartWindows); - const endsWithGlobStar = exclude.endsWith(ParcelWatcherService.GLOB_MARKERS.GlobStarPathEndPosix) || exclude.endsWith(ParcelWatcherService.GLOB_MARKERS.GlobStarPathEndWindows); + const startsWithGlobStar = exclude.startsWith(ParcelWatcher.GLOB_MARKERS.GlobStarPathStartPosix) || exclude.startsWith(ParcelWatcher.GLOB_MARKERS.GlobStarPathStartWindows); + const endsWithGlobStar = exclude.endsWith(ParcelWatcher.GLOB_MARKERS.GlobStarPathEndPosix) || exclude.endsWith(ParcelWatcher.GLOB_MARKERS.GlobStarPathEndWindows); if (startsWithGlobStar || endsWithGlobStar) { if (startsWithGlobStar && endsWithGlobStar) { - normalizedExclude = exclude.substring(ParcelWatcherService.GLOB_MARKERS.GlobStarPathStartPosix.length, exclude.length - ParcelWatcherService.GLOB_MARKERS.GlobStarPathEndPosix.length); + normalizedExclude = exclude.substring(ParcelWatcher.GLOB_MARKERS.GlobStarPathStartPosix.length, exclude.length - ParcelWatcher.GLOB_MARKERS.GlobStarPathEndPosix.length); } else if (startsWithGlobStar) { - normalizedExclude = exclude.substring(ParcelWatcherService.GLOB_MARKERS.GlobStarPathStartPosix.length); + normalizedExclude = exclude.substring(ParcelWatcher.GLOB_MARKERS.GlobStarPathStartPosix.length); } else { - normalizedExclude = exclude.substring(0, exclude.length - ParcelWatcherService.GLOB_MARKERS.GlobStarPathEndPosix.length); + normalizedExclude = exclude.substring(0, exclude.length - ParcelWatcher.GLOB_MARKERS.GlobStarPathEndPosix.length); } } @@ -201,9 +201,9 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // Examples: // - node_modules/* (full form: **/node_modules/*/**) if (isLinux && normalizedExclude) { - const endsWithStar = normalizedExclude?.endsWith(ParcelWatcherService.GLOB_MARKERS.StarPathEndPosix); + const endsWithStar = normalizedExclude?.endsWith(ParcelWatcher.GLOB_MARKERS.StarPathEndPosix); if (endsWithStar) { - normalizedExclude = normalizedExclude.substring(0, normalizedExclude.length - ParcelWatcherService.GLOB_MARKERS.StarPathEndPosix.length); + normalizedExclude = normalizedExclude.substring(0, normalizedExclude.length - ParcelWatcher.GLOB_MARKERS.StarPathEndPosix.length); } } } @@ -214,7 +214,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService normalizedExclude = exclude; } - if (!normalizedExclude || normalizedExclude.includes(ParcelWatcherService.GLOB_MARKERS.Star)) { + if (!normalizedExclude || normalizedExclude.includes(ParcelWatcher.GLOB_MARKERS.Star)) { continue; // skip for parcel (will be applied later by our glob matching) } @@ -249,7 +249,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService const snapshotFile = join(tmpdir(), `vscode-watcher-snapshot-${generateUuid()}`); // Remember as watcher instance - const watcher: IWatcher = { + const watcher: IParcelWatcherInstance = { request, ready: instance.p, restarts, @@ -283,7 +283,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // We already ran before, check for events since if (counter > 1) { - const parcelEvents = await parcelWatcher.getEventsSince(realPath, snapshotFile, { ignore, backend: ParcelWatcherService.PARCEL_WATCHER_BACKEND }); + const parcelEvents = await parcelWatcher.getEventsSince(realPath, snapshotFile, { ignore, backend: ParcelWatcher.PARCEL_WATCHER_BACKEND }); if (cts.token.isCancellationRequested) { return; @@ -294,7 +294,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService } // Store a snapshot of files to the snapshot file - await parcelWatcher.writeSnapshot(realPath, snapshotFile, { ignore, backend: ParcelWatcherService.PARCEL_WATCHER_BACKEND }); + await parcelWatcher.writeSnapshot(realPath, snapshotFile, { ignore, backend: ParcelWatcher.PARCEL_WATCHER_BACKEND }); // Signal we are ready now when the first snapshot was written if (counter === 1) { @@ -317,7 +317,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService const instance = new DeferredPromise(); // Remember as watcher instance - const watcher: IWatcher = { + const watcher: IParcelWatcherInstance = { request, ready: instance.p, restarts, @@ -354,10 +354,10 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // Handle & emit events this.onParcelEvents(parcelEvents, watcher, excludePatterns, realPathDiffers, realPathLength); }, { - backend: ParcelWatcherService.PARCEL_WATCHER_BACKEND, + backend: ParcelWatcher.PARCEL_WATCHER_BACKEND, ignore }).then(parcelWatcher => { - this.debug(`Started watching: '${realPath}' with backend '${ParcelWatcherService.PARCEL_WATCHER_BACKEND}' and native excludes '${ignore?.join(', ')}'`); + this.debug(`Started watching: '${realPath}' with backend '${ParcelWatcher.PARCEL_WATCHER_BACKEND}' and native excludes '${ignore?.join(', ')}'`); instance.complete(parcelWatcher); }).catch(error => { @@ -367,7 +367,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService }); } - private onParcelEvents(parcelEvents: parcelWatcher.Event[], watcher: IWatcher, excludes: ParsedPattern[], realPathDiffers: boolean, realPathLength: number): void { + private onParcelEvents(parcelEvents: parcelWatcher.Event[], watcher: IParcelWatcherInstance, excludes: ParsedPattern[], realPathDiffers: boolean, realPathLength: number): void { if (parcelEvents.length === 0) { return; } @@ -397,7 +397,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService const events: IDiskFileChange[] = []; for (const { path, type: parcelEventType } of parcelEvents) { - const type = ParcelWatcherService.MAP_PARCEL_WATCHER_ACTION_TO_FILE_CHANGE.get(parcelEventType)!; + const type = ParcelWatcher.MAP_PARCEL_WATCHER_ACTION_TO_FILE_CHANGE.get(parcelEventType)!; if (this.verboseLogging) { this.log(`${type === FileChangeType.ADDED ? '[ADDED]' : type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${path}`); } @@ -507,7 +507,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService }); } - private onWatchedPathDeleted(watcher: IWatcher): void { + private onWatchedPathDeleted(watcher: IParcelWatcherInstance): void { this.warn('Watcher shutdown because watched path got deleted', watcher); const parentPath = dirname(watcher.request.path); @@ -536,7 +536,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService } } - private onUnexpectedError(error: unknown, watcher?: IWatcher): void { + private onUnexpectedError(error: unknown, watcher?: IParcelWatcherInstance): void { const msg = toErrorMessage(error); // Specially handle ENOSPC errors that can happen when @@ -570,7 +570,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService this.watchers.clear(); } - protected restartWatching(watcher: IWatcher, delay = 800): void { + protected restartWatching(watcher: IParcelWatcherInstance, delay = 800): void { // Restart watcher delayed to accomodate for // changes on disk that have triggered the @@ -656,11 +656,11 @@ export class ParcelWatcherService extends Disposable implements IWatcherService this._onDidLogMessage.fire({ type: 'trace', message: this.toMessage(message) }); } - private warn(message: string, watcher?: IWatcher) { + private warn(message: string, watcher?: IParcelWatcherInstance) { this._onDidLogMessage.fire({ type: 'warn', message: this.toMessage(message, watcher) }); } - private error(message: string, watcher: IWatcher | undefined) { + private error(message: string, watcher: IParcelWatcherInstance | undefined) { this._onDidLogMessage.fire({ type: 'error', message: this.toMessage(message, watcher) }); } @@ -668,7 +668,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService this._onDidLogMessage.fire({ type: 'debug', message: this.toMessage(message) }); } - private toMessage(message: string, watcher?: IWatcher): string { + private toMessage(message: string, watcher?: IParcelWatcherInstance): string { return watcher ? `[File Watcher (parcel)] ${message} (path: ${watcher.request.path})` : `[File Watcher (parcel)] ${message}`; } } diff --git a/src/vs/platform/files/node/watcher/parcel/watcherService.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcherClient.ts similarity index 77% rename from src/vs/platform/files/node/watcher/parcel/watcherService.ts rename to src/vs/platform/files/node/watcher/parcel/parcelWatcherClient.ts index 94eaf9302d131..23132bf886ca2 100644 --- a/src/vs/platform/files/node/watcher/parcel/watcherService.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcherClient.ts @@ -7,9 +7,9 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; import { getNextTickChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { AbstractWatcherService, IDiskFileChange, ILogMessage, IWatcherService } from 'vs/platform/files/common/watcher'; +import { AbstractRecursiveWatcherClient, IDiskFileChange, ILogMessage, IRecursiveWatcher } from 'vs/platform/files/common/watcher'; -export class FileWatcher extends AbstractWatcherService { +export class ParcelWatcherClient extends AbstractRecursiveWatcherClient { constructor( onFileChanges: (changes: IDiskFileChange[]) => void, @@ -21,7 +21,7 @@ export class FileWatcher extends AbstractWatcherService { this.init(); } - protected override createService(disposables: DisposableStore): IWatcherService { + protected override createWatcher(disposables: DisposableStore): IRecursiveWatcher { // Fork the parcel file watcher and build a client around // its server for passing over requests and receiving events. @@ -29,9 +29,9 @@ export class FileWatcher extends AbstractWatcherService { FileAccess.asFileUri('bootstrap-fork', require).fsPath, { serverName: 'File Watcher (parcel, node.js)', - args: ['--type=watcherServiceParcel'], + args: ['--type=parcelWatcher'], env: { - VSCODE_AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/parcel/watcherApp', + VSCODE_AMD_ENTRYPOINT: 'vs/platform/files/node/watcher/parcel/parcelWatcherMain', VSCODE_PIPE_LOGGING: 'true', VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client } @@ -41,6 +41,6 @@ export class FileWatcher extends AbstractWatcherService { // React on unexpected termination of the watcher process disposables.add(client.onDidProcessExit(({ code, signal }) => this.onError(`terminated by itself with code ${code}, signal: ${signal}`))); - return ProxyChannel.toService(getNextTickChannel(client.getChannel('watcher'))); + return ProxyChannel.toService(getNextTickChannel(client.getChannel('watcher'))); } } diff --git a/src/vs/platform/files/node/watcher/nsfw/watcherApp.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcherMain.ts similarity index 81% rename from src/vs/platform/files/node/watcher/nsfw/watcherApp.ts rename to src/vs/platform/files/node/watcher/parcel/parcelWatcherMain.ts index 2b24f9dd0c1da..bc9b04171c26f 100644 --- a/src/vs/platform/files/node/watcher/nsfw/watcherApp.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcherMain.ts @@ -5,8 +5,8 @@ import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; -import { NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/nsfwWatcherService'; +import { ParcelWatcher } from 'vs/platform/files/node/watcher/parcel/parcelWatcher'; const server = new Server('watcher'); -const service = new NsfwWatcherService(); +const service = new ParcelWatcher(); server.registerChannel('watcher', ProxyChannel.fromService(service)); diff --git a/src/vs/platform/files/node/watcher/parcel/watcherApp.ts b/src/vs/platform/files/node/watcher/parcel/watcherApp.ts deleted file mode 100644 index 65a3d1bfe7e28..0000000000000 --- a/src/vs/platform/files/node/watcher/parcel/watcherApp.ts +++ /dev/null @@ -1,12 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; -import { ParcelWatcherService } from 'vs/platform/files/node/watcher/parcel/parcelWatcherService'; - -const server = new Server('watcher'); -const service = new ParcelWatcherService(); -server.registerChannel('watcher', ProxyChannel.fromService(service)); diff --git a/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts b/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts index ae61017ed1fa9..f499dd1424b07 100644 --- a/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts @@ -12,12 +12,12 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { Promises, RimRafMode } from 'vs/base/node/pfs'; import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { FileChangeType } from 'vs/platform/files/common/files'; -import { IWatcher, ParcelWatcherService } from 'vs/platform/files/node/watcher/parcel/parcelWatcherService'; +import { IParcelWatcherInstance, ParcelWatcher } from 'vs/platform/files/node/watcher/parcel/parcelWatcher'; import { IWatchRequest } from 'vs/platform/files/common/watcher'; flakySuite('Recursive Watcher (parcel)', () => { - class TestParcelWatcherService extends ParcelWatcherService { + class TestParcelWatcher extends ParcelWatcher { testNormalizePaths(paths: string[]): string[] { @@ -44,33 +44,33 @@ flakySuite('Recursive Watcher (parcel)', () => { return super.toExcludePaths(path, excludes); } - override restartWatching(watcher: IWatcher, delay = 10): void { + override restartWatching(watcher: IParcelWatcherInstance, delay = 10): void { return super.restartWatching(watcher, delay); } } let testDir: string; - let service: TestParcelWatcherService; + let watcher: TestParcelWatcher; let loggingEnabled = false; function enableLogging(enable: boolean) { loggingEnabled = enable; - service?.setVerboseLogging(enable); + watcher?.setVerboseLogging(enable); } enableLogging(false); setup(async () => { - service = new TestParcelWatcherService(); + watcher = new TestParcelWatcher(); - service.onDidLogMessage(e => { + watcher.onDidLogMessage(e => { if (loggingEnabled) { console.log(`[recursive watcher test message] ${e.message}`); } }); - service.onDidError(e => { + watcher.onDidError(e => { if (loggingEnabled) { console.log(`[recursive watcher test error] ${e}`); } @@ -84,7 +84,7 @@ flakySuite('Recursive Watcher (parcel)', () => { }); teardown(async () => { - await service.stop(); + await watcher.stop(); // Possible that the file watcher is still holding // onto the folders on Windows specifically and the @@ -101,7 +101,7 @@ flakySuite('Recursive Watcher (parcel)', () => { } } - async function awaitEvent(service: TestParcelWatcherService, path: string, type: FileChangeType, failOnEventReason?: string): Promise { + async function awaitEvent(service: TestParcelWatcher, path: string, type: FileChangeType, failOnEventReason?: string): Promise { if (loggingEnabled) { console.log(`Awaiting change type '${toMsg(type)}' on file '${path}'`); } @@ -130,7 +130,7 @@ flakySuite('Recursive Watcher (parcel)', () => { await timeout(1); } - function awaitMessage(service: TestParcelWatcherService, type: 'trace' | 'warn' | 'error' | 'info' | 'debug'): Promise { + function awaitMessage(service: TestParcelWatcher, type: 'trace' | 'warn' | 'error' | 'info' | 'debug'): Promise { if (loggingEnabled) { console.log(`Awaiting message of type ${type}`); } @@ -147,25 +147,25 @@ flakySuite('Recursive Watcher (parcel)', () => { } test('basics', async function () { - await service.watch([{ path: testDir, excludes: [] }]); + await watcher.watch([{ path: testDir, excludes: [] }]); // New file const newFilePath = join(testDir, 'deep', 'newFile.txt'); - let changeFuture: Promise = awaitEvent(service, newFilePath, FileChangeType.ADDED); + let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); await Promises.writeFile(newFilePath, 'Hello World'); await changeFuture; // New folder const newFolderPath = join(testDir, 'deep', 'New Folder'); - changeFuture = awaitEvent(service, newFolderPath, FileChangeType.ADDED); + changeFuture = awaitEvent(watcher, newFolderPath, FileChangeType.ADDED); await Promises.mkdir(newFolderPath); await changeFuture; // Rename file let renamedFilePath = join(testDir, 'deep', 'renamedFile.txt'); changeFuture = Promise.all([ - awaitEvent(service, newFilePath, FileChangeType.DELETED), - awaitEvent(service, renamedFilePath, FileChangeType.ADDED) + awaitEvent(watcher, newFilePath, FileChangeType.DELETED), + awaitEvent(watcher, renamedFilePath, FileChangeType.ADDED) ]); await Promises.rename(newFilePath, renamedFilePath); await changeFuture; @@ -173,8 +173,8 @@ flakySuite('Recursive Watcher (parcel)', () => { // Rename folder let renamedFolderPath = join(testDir, 'deep', 'Renamed Folder'); changeFuture = Promise.all([ - awaitEvent(service, newFolderPath, FileChangeType.DELETED), - awaitEvent(service, renamedFolderPath, FileChangeType.ADDED) + awaitEvent(watcher, newFolderPath, FileChangeType.DELETED), + awaitEvent(watcher, renamedFolderPath, FileChangeType.ADDED) ]); await Promises.rename(newFolderPath, renamedFolderPath); await changeFuture; @@ -182,8 +182,8 @@ flakySuite('Recursive Watcher (parcel)', () => { // Rename file (same name, different case) const caseRenamedFilePath = join(testDir, 'deep', 'RenamedFile.txt'); changeFuture = Promise.all([ - awaitEvent(service, renamedFilePath, FileChangeType.DELETED), - awaitEvent(service, caseRenamedFilePath, FileChangeType.ADDED) + awaitEvent(watcher, renamedFilePath, FileChangeType.DELETED), + awaitEvent(watcher, caseRenamedFilePath, FileChangeType.ADDED) ]); await Promises.rename(renamedFilePath, caseRenamedFilePath); await changeFuture; @@ -192,8 +192,8 @@ flakySuite('Recursive Watcher (parcel)', () => { // Rename folder (same name, different case) const caseRenamedFolderPath = join(testDir, 'deep', 'REnamed Folder'); changeFuture = Promise.all([ - awaitEvent(service, renamedFolderPath, FileChangeType.DELETED), - awaitEvent(service, caseRenamedFolderPath, FileChangeType.ADDED) + awaitEvent(watcher, renamedFolderPath, FileChangeType.DELETED), + awaitEvent(watcher, caseRenamedFolderPath, FileChangeType.ADDED) ]); await Promises.rename(renamedFolderPath, caseRenamedFolderPath); await changeFuture; @@ -202,8 +202,8 @@ flakySuite('Recursive Watcher (parcel)', () => { // Move file const movedFilepath = join(testDir, 'movedFile.txt'); changeFuture = Promise.all([ - awaitEvent(service, renamedFilePath, FileChangeType.DELETED), - awaitEvent(service, movedFilepath, FileChangeType.ADDED) + awaitEvent(watcher, renamedFilePath, FileChangeType.DELETED), + awaitEvent(watcher, movedFilepath, FileChangeType.ADDED) ]); await Promises.rename(renamedFilePath, movedFilepath); await changeFuture; @@ -211,32 +211,32 @@ flakySuite('Recursive Watcher (parcel)', () => { // Move folder const movedFolderpath = join(testDir, 'Moved Folder'); changeFuture = Promise.all([ - awaitEvent(service, renamedFolderPath, FileChangeType.DELETED), - awaitEvent(service, movedFolderpath, FileChangeType.ADDED) + awaitEvent(watcher, renamedFolderPath, FileChangeType.DELETED), + awaitEvent(watcher, movedFolderpath, FileChangeType.ADDED) ]); await Promises.rename(renamedFolderPath, movedFolderpath); await changeFuture; // Copy file const copiedFilepath = join(testDir, 'deep', 'copiedFile.txt'); - changeFuture = awaitEvent(service, copiedFilepath, FileChangeType.ADDED); + changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.ADDED); await Promises.copyFile(movedFilepath, copiedFilepath); await changeFuture; // Copy folder const copiedFolderpath = join(testDir, 'deep', 'Copied Folder'); - changeFuture = awaitEvent(service, copiedFolderpath, FileChangeType.ADDED); + changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.ADDED); await Promises.copy(movedFolderpath, copiedFolderpath, { preserveSymlinks: false }); await changeFuture; // Change file - changeFuture = awaitEvent(service, copiedFilepath, FileChangeType.UPDATED); + changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.UPDATED); await Promises.writeFile(copiedFilepath, 'Hello Change'); await changeFuture; // Create new file const anotherNewFilePath = join(testDir, 'deep', 'anotherNewFile.txt'); - changeFuture = awaitEvent(service, anotherNewFilePath, FileChangeType.ADDED); + changeFuture = awaitEvent(watcher, anotherNewFilePath, FileChangeType.ADDED); await Promises.writeFile(anotherNewFilePath, 'Hello Another World'); await changeFuture; @@ -246,65 +246,65 @@ flakySuite('Recursive Watcher (parcel)', () => { if (!isMacintosh) { // Read file does not emit event - changeFuture = awaitEvent(service, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-read-file'); + changeFuture = awaitEvent(watcher, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-read-file'); await Promises.readFile(anotherNewFilePath); await Promise.race([timeout(100), changeFuture]); // Stat file does not emit event - changeFuture = awaitEvent(service, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); + changeFuture = awaitEvent(watcher, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); await Promises.stat(anotherNewFilePath); await Promise.race([timeout(100), changeFuture]); // Stat folder does not emit event - changeFuture = awaitEvent(service, copiedFolderpath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); + changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); await Promises.stat(copiedFolderpath); await Promise.race([timeout(100), changeFuture]); } // Delete file - changeFuture = awaitEvent(service, copiedFilepath, FileChangeType.DELETED); + changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.DELETED); await Promises.unlink(copiedFilepath); await changeFuture; // Delete folder - changeFuture = awaitEvent(service, copiedFolderpath, FileChangeType.DELETED); + changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.DELETED); await Promises.rmdir(copiedFolderpath); await changeFuture; }); (isMacintosh /* this test seems not possible with fsevents backend */ ? test.skip : test)('basics (atomic writes)', async function () { - await service.watch([{ path: testDir, excludes: [] }]); + await watcher.watch([{ path: testDir, excludes: [] }]); // Delete + Recreate file const newFilePath = join(testDir, 'deep', 'conway.js'); - let changeFuture: Promise = awaitEvent(service, newFilePath, FileChangeType.UPDATED); + let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED); await Promises.unlink(newFilePath); Promises.writeFile(newFilePath, 'Hello Atomic World'); await changeFuture; }); (!isLinux /* polling is only used in linux environments (WSL) */ ? test.skip : test)('basics (polling)', async function () { - await service.watch([{ path: testDir, excludes: [], pollingInterval: 100 }]); + await watcher.watch([{ path: testDir, excludes: [], pollingInterval: 100 }]); // New file const newFilePath = join(testDir, 'deep', 'newFile.txt'); - let changeFuture: Promise = awaitEvent(service, newFilePath, FileChangeType.ADDED); + let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); await Promises.writeFile(newFilePath, 'Hello World'); await changeFuture; // Change file - changeFuture = awaitEvent(service, newFilePath, FileChangeType.UPDATED); + changeFuture = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED); await Promises.writeFile(newFilePath, 'Hello Change'); await changeFuture; // Delete file - changeFuture = awaitEvent(service, newFilePath, FileChangeType.DELETED); + changeFuture = awaitEvent(watcher, newFilePath, FileChangeType.DELETED); await Promises.unlink(newFilePath); await changeFuture; }); test('multiple events', async function () { - await service.watch([{ path: testDir, excludes: [] }]); + await watcher.watch([{ path: testDir, excludes: [] }]); await Promises.mkdir(join(testDir, 'deep-multiple')); // multiple add @@ -316,12 +316,12 @@ flakySuite('Recursive Watcher (parcel)', () => { const newFilePath5 = join(testDir, 'deep-multiple', 'newFile-2.txt'); const newFilePath6 = join(testDir, 'deep-multiple', 'newFile-3.txt'); - const addedFuture1: Promise = awaitEvent(service, newFilePath1, FileChangeType.ADDED); - const addedFuture2: Promise = awaitEvent(service, newFilePath2, FileChangeType.ADDED); - const addedFuture3: Promise = awaitEvent(service, newFilePath3, FileChangeType.ADDED); - const addedFuture4: Promise = awaitEvent(service, newFilePath4, FileChangeType.ADDED); - const addedFuture5: Promise = awaitEvent(service, newFilePath5, FileChangeType.ADDED); - const addedFuture6: Promise = awaitEvent(service, newFilePath6, FileChangeType.ADDED); + const addedFuture1: Promise = awaitEvent(watcher, newFilePath1, FileChangeType.ADDED); + const addedFuture2: Promise = awaitEvent(watcher, newFilePath2, FileChangeType.ADDED); + const addedFuture3: Promise = awaitEvent(watcher, newFilePath3, FileChangeType.ADDED); + const addedFuture4: Promise = awaitEvent(watcher, newFilePath4, FileChangeType.ADDED); + const addedFuture5: Promise = awaitEvent(watcher, newFilePath5, FileChangeType.ADDED); + const addedFuture6: Promise = awaitEvent(watcher, newFilePath6, FileChangeType.ADDED); await Promise.all([ await Promises.writeFile(newFilePath1, 'Hello World 1'), @@ -336,12 +336,12 @@ flakySuite('Recursive Watcher (parcel)', () => { // multiple change - const changeFuture1: Promise = awaitEvent(service, newFilePath1, FileChangeType.UPDATED); - const changeFuture2: Promise = awaitEvent(service, newFilePath2, FileChangeType.UPDATED); - const changeFuture3: Promise = awaitEvent(service, newFilePath3, FileChangeType.UPDATED); - const changeFuture4: Promise = awaitEvent(service, newFilePath4, FileChangeType.UPDATED); - const changeFuture5: Promise = awaitEvent(service, newFilePath5, FileChangeType.UPDATED); - const changeFuture6: Promise = awaitEvent(service, newFilePath6, FileChangeType.UPDATED); + const changeFuture1: Promise = awaitEvent(watcher, newFilePath1, FileChangeType.UPDATED); + const changeFuture2: Promise = awaitEvent(watcher, newFilePath2, FileChangeType.UPDATED); + const changeFuture3: Promise = awaitEvent(watcher, newFilePath3, FileChangeType.UPDATED); + const changeFuture4: Promise = awaitEvent(watcher, newFilePath4, FileChangeType.UPDATED); + const changeFuture5: Promise = awaitEvent(watcher, newFilePath5, FileChangeType.UPDATED); + const changeFuture6: Promise = awaitEvent(watcher, newFilePath6, FileChangeType.UPDATED); await Promise.all([ await Promises.writeFile(newFilePath1, 'Hello Update 1'), @@ -356,10 +356,10 @@ flakySuite('Recursive Watcher (parcel)', () => { // copy with multiple files - const copyFuture1: Promise = awaitEvent(service, join(testDir, 'deep-multiple-copy', 'newFile-1.txt'), FileChangeType.ADDED); - const copyFuture2: Promise = awaitEvent(service, join(testDir, 'deep-multiple-copy', 'newFile-2.txt'), FileChangeType.ADDED); - const copyFuture3: Promise = awaitEvent(service, join(testDir, 'deep-multiple-copy', 'newFile-3.txt'), FileChangeType.ADDED); - const copyFuture4: Promise = awaitEvent(service, join(testDir, 'deep-multiple-copy'), FileChangeType.ADDED); + const copyFuture1: Promise = awaitEvent(watcher, join(testDir, 'deep-multiple-copy', 'newFile-1.txt'), FileChangeType.ADDED); + const copyFuture2: Promise = awaitEvent(watcher, join(testDir, 'deep-multiple-copy', 'newFile-2.txt'), FileChangeType.ADDED); + const copyFuture3: Promise = awaitEvent(watcher, join(testDir, 'deep-multiple-copy', 'newFile-3.txt'), FileChangeType.ADDED); + const copyFuture4: Promise = awaitEvent(watcher, join(testDir, 'deep-multiple-copy'), FileChangeType.ADDED); await Promises.copy(join(testDir, 'deep-multiple'), join(testDir, 'deep-multiple-copy'), { preserveSymlinks: false }); @@ -367,12 +367,12 @@ flakySuite('Recursive Watcher (parcel)', () => { // multiple delete (single files) - const deleteFuture1: Promise = awaitEvent(service, newFilePath1, FileChangeType.DELETED); - const deleteFuture2: Promise = awaitEvent(service, newFilePath2, FileChangeType.DELETED); - const deleteFuture3: Promise = awaitEvent(service, newFilePath3, FileChangeType.DELETED); - const deleteFuture4: Promise = awaitEvent(service, newFilePath4, FileChangeType.DELETED); - const deleteFuture5: Promise = awaitEvent(service, newFilePath5, FileChangeType.DELETED); - const deleteFuture6: Promise = awaitEvent(service, newFilePath6, FileChangeType.DELETED); + const deleteFuture1: Promise = awaitEvent(watcher, newFilePath1, FileChangeType.DELETED); + const deleteFuture2: Promise = awaitEvent(watcher, newFilePath2, FileChangeType.DELETED); + const deleteFuture3: Promise = awaitEvent(watcher, newFilePath3, FileChangeType.DELETED); + const deleteFuture4: Promise = awaitEvent(watcher, newFilePath4, FileChangeType.DELETED); + const deleteFuture5: Promise = awaitEvent(watcher, newFilePath5, FileChangeType.DELETED); + const deleteFuture6: Promise = awaitEvent(watcher, newFilePath6, FileChangeType.DELETED); await Promise.all([ await Promises.unlink(newFilePath1), @@ -387,8 +387,8 @@ flakySuite('Recursive Watcher (parcel)', () => { // multiple delete (folder) - const deleteFolderFuture1: Promise = awaitEvent(service, join(testDir, 'deep-multiple'), FileChangeType.DELETED); - const deleteFolderFuture2: Promise = awaitEvent(service, join(testDir, 'deep-multiple-copy'), FileChangeType.DELETED); + const deleteFolderFuture1: Promise = awaitEvent(watcher, join(testDir, 'deep-multiple'), FileChangeType.DELETED); + const deleteFolderFuture2: Promise = awaitEvent(watcher, join(testDir, 'deep-multiple-copy'), FileChangeType.DELETED); await Promise.all([Promises.rm(join(testDir, 'deep-multiple'), RimRafMode.UNLINK), Promises.rm(join(testDir, 'deep-multiple-copy'), RimRafMode.UNLINK)]); @@ -396,35 +396,35 @@ flakySuite('Recursive Watcher (parcel)', () => { }); test('subsequent watch updates watchers (path)', async function () { - await service.watch([{ path: testDir, excludes: [join(realpathSync(testDir), 'unrelated')] }]); + await watcher.watch([{ path: testDir, excludes: [join(realpathSync(testDir), 'unrelated')] }]); // New file (*.txt) let newTextFilePath = join(testDir, 'deep', 'newFile.txt'); - let changeFuture: Promise = awaitEvent(service, newTextFilePath, FileChangeType.ADDED); + let changeFuture: Promise = awaitEvent(watcher, newTextFilePath, FileChangeType.ADDED); await Promises.writeFile(newTextFilePath, 'Hello World'); await changeFuture; - await service.watch([{ path: join(testDir, 'deep'), excludes: [join(realpathSync(testDir), 'unrelated')] }]); + await watcher.watch([{ path: join(testDir, 'deep'), excludes: [join(realpathSync(testDir), 'unrelated')] }]); newTextFilePath = join(testDir, 'deep', 'newFile2.txt'); - changeFuture = awaitEvent(service, newTextFilePath, FileChangeType.ADDED); + changeFuture = awaitEvent(watcher, newTextFilePath, FileChangeType.ADDED); await Promises.writeFile(newTextFilePath, 'Hello World'); await changeFuture; - await service.watch([{ path: join(testDir, 'deep'), excludes: [realpathSync(testDir)] }]); - await service.watch([{ path: join(testDir, 'deep'), excludes: [] }]); + await watcher.watch([{ path: join(testDir, 'deep'), excludes: [realpathSync(testDir)] }]); + await watcher.watch([{ path: join(testDir, 'deep'), excludes: [] }]); newTextFilePath = join(testDir, 'deep', 'newFile3.txt'); - changeFuture = awaitEvent(service, newTextFilePath, FileChangeType.ADDED); + changeFuture = awaitEvent(watcher, newTextFilePath, FileChangeType.ADDED); await Promises.writeFile(newTextFilePath, 'Hello World'); await changeFuture; }); test('subsequent watch updates watchers (excludes)', async function () { - await service.watch([{ path: testDir, excludes: [realpathSync(testDir)] }]); - await service.watch([{ path: testDir, excludes: [] }]); + await watcher.watch([{ path: testDir, excludes: [realpathSync(testDir)] }]); + await watcher.watch([{ path: testDir, excludes: [] }]); // New file (*.txt) let newTextFilePath = join(testDir, 'deep', 'newFile.txt'); - let changeFuture: Promise = awaitEvent(service, newTextFilePath, FileChangeType.ADDED); + let changeFuture: Promise = awaitEvent(watcher, newTextFilePath, FileChangeType.ADDED); await Promises.writeFile(newTextFilePath, 'Hello World'); await changeFuture; }); @@ -434,11 +434,11 @@ flakySuite('Recursive Watcher (parcel)', () => { const linkTarget = join(testDir, 'deep'); await Promises.symlink(linkTarget, link); - await service.watch([{ path: link, excludes: [] }]); + await watcher.watch([{ path: link, excludes: [] }]); // New file const newFilePath = join(link, 'newFile.txt'); - let changeFuture: Promise = awaitEvent(service, newFilePath, FileChangeType.ADDED); + let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); await Promises.writeFile(newFilePath, 'Hello World'); await changeFuture; }); @@ -448,11 +448,11 @@ flakySuite('Recursive Watcher (parcel)', () => { const linkTarget = join(testDir, 'deep'); await Promises.symlink(linkTarget, link); - await service.watch([{ path: testDir, excludes: [] }, { path: link, excludes: [] }]); + await watcher.watch([{ path: testDir, excludes: [] }, { path: link, excludes: [] }]); // New file const newFilePath = join(link, 'newFile.txt'); - let changeFuture: Promise = awaitEvent(service, newFilePath, FileChangeType.ADDED); + let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); await Promises.writeFile(newFilePath, 'Hello World'); await changeFuture; }); @@ -460,11 +460,11 @@ flakySuite('Recursive Watcher (parcel)', () => { (isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing', async function () { const deepWrongCasedPath = join(testDir, 'DEEP'); - await service.watch([{ path: deepWrongCasedPath, excludes: [] }]); + await watcher.watch([{ path: deepWrongCasedPath, excludes: [] }]); // New file const newFilePath = join(deepWrongCasedPath, 'newFile.txt'); - let changeFuture: Promise = awaitEvent(service, newFilePath, FileChangeType.ADDED); + let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); await Promises.writeFile(newFilePath, 'Hello World'); await changeFuture; }); @@ -472,54 +472,54 @@ flakySuite('Recursive Watcher (parcel)', () => { test('invalid folder does not explode', async function () { const invalidPath = join(testDir, 'invalid'); - await service.watch([{ path: invalidPath, excludes: [] }]); + await watcher.watch([{ path: invalidPath, excludes: [] }]); }); test('deleting watched path is handled properly', async function () { const watchedPath = join(testDir, 'deep'); - await service.watch([{ path: watchedPath, excludes: [] }]); + await watcher.watch([{ path: watchedPath, excludes: [] }]); // Delete watched path and await - const warnFuture = awaitMessage(service, 'warn'); + const warnFuture = awaitMessage(watcher, 'warn'); await Promises.rm(watchedPath, RimRafMode.UNLINK); await warnFuture; // Restore watched path await Promises.mkdir(watchedPath); await timeout(1500); // restart is delayed - await service.whenReady(); + await watcher.whenReady(); // Verify events come in again const newFilePath = join(watchedPath, 'newFile.txt'); - const changeFuture = awaitEvent(service, newFilePath, FileChangeType.ADDED); + const changeFuture = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); await Promises.writeFile(newFilePath, 'Hello World'); await changeFuture; }); test('should not exclude roots that do not overlap', () => { if (isWindows) { - assert.deepStrictEqual(service.testNormalizePaths(['C:\\a']), ['C:\\a']); - assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\b']), ['C:\\a', 'C:\\b']); - assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\b', 'C:\\c\\d\\e']), ['C:\\a', 'C:\\b', 'C:\\c\\d\\e']); + assert.deepStrictEqual(watcher.testNormalizePaths(['C:\\a']), ['C:\\a']); + assert.deepStrictEqual(watcher.testNormalizePaths(['C:\\a', 'C:\\b']), ['C:\\a', 'C:\\b']); + assert.deepStrictEqual(watcher.testNormalizePaths(['C:\\a', 'C:\\b', 'C:\\c\\d\\e']), ['C:\\a', 'C:\\b', 'C:\\c\\d\\e']); } else { - assert.deepStrictEqual(service.testNormalizePaths(['/a']), ['/a']); - assert.deepStrictEqual(service.testNormalizePaths(['/a', '/b']), ['/a', '/b']); - assert.deepStrictEqual(service.testNormalizePaths(['/a', '/b', '/c/d/e']), ['/a', '/b', '/c/d/e']); + assert.deepStrictEqual(watcher.testNormalizePaths(['/a']), ['/a']); + assert.deepStrictEqual(watcher.testNormalizePaths(['/a', '/b']), ['/a', '/b']); + assert.deepStrictEqual(watcher.testNormalizePaths(['/a', '/b', '/c/d/e']), ['/a', '/b', '/c/d/e']); } }); test('should remove sub-folders of other paths', () => { if (isWindows) { - assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\a\\b']), ['C:\\a']); - assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']); - assert.deepStrictEqual(service.testNormalizePaths(['C:\\b\\a', 'C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']); - assert.deepStrictEqual(service.testNormalizePaths(['C:\\a', 'C:\\a\\b', 'C:\\a\\c\\d']), ['C:\\a']); + assert.deepStrictEqual(watcher.testNormalizePaths(['C:\\a', 'C:\\a\\b']), ['C:\\a']); + assert.deepStrictEqual(watcher.testNormalizePaths(['C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']); + assert.deepStrictEqual(watcher.testNormalizePaths(['C:\\b\\a', 'C:\\a', 'C:\\b', 'C:\\a\\b']), ['C:\\a', 'C:\\b']); + assert.deepStrictEqual(watcher.testNormalizePaths(['C:\\a', 'C:\\a\\b', 'C:\\a\\c\\d']), ['C:\\a']); } else { - assert.deepStrictEqual(service.testNormalizePaths(['/a', '/a/b']), ['/a']); - assert.deepStrictEqual(service.testNormalizePaths(['/a', '/b', '/a/b']), ['/a', '/b']); - assert.deepStrictEqual(service.testNormalizePaths(['/b/a', '/a', '/b', '/a/b']), ['/a', '/b']); - assert.deepStrictEqual(service.testNormalizePaths(['/a', '/a/b', '/a/c/d']), ['/a']); + assert.deepStrictEqual(watcher.testNormalizePaths(['/a', '/a/b']), ['/a']); + assert.deepStrictEqual(watcher.testNormalizePaths(['/a', '/b', '/a/b']), ['/a', '/b']); + assert.deepStrictEqual(watcher.testNormalizePaths(['/b/a', '/a', '/b', '/a/b']), ['/a', '/b']); + assert.deepStrictEqual(watcher.testNormalizePaths(['/a', '/a/b', '/a/c/d']), ['/a']); } }); @@ -527,16 +527,16 @@ flakySuite('Recursive Watcher (parcel)', () => { // undefined / empty - assert.strictEqual(service.toExcludePaths(testDir, undefined), undefined); - assert.strictEqual(service.toExcludePaths(testDir, []), undefined); + assert.strictEqual(watcher.toExcludePaths(testDir, undefined), undefined); + assert.strictEqual(watcher.toExcludePaths(testDir, []), undefined); // absolute paths - let excludes = service.toExcludePaths(testDir, [testDir]); + let excludes = watcher.toExcludePaths(testDir, [testDir]); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], testDir); - excludes = service.toExcludePaths(testDir, [`${testDir}${sep}`, join(testDir, 'foo', 'bar'), `${join(testDir, 'other', 'deep')}${sep}`]); + excludes = watcher.toExcludePaths(testDir, [`${testDir}${sep}`, join(testDir, 'foo', 'bar'), `${join(testDir, 'other', 'deep')}${sep}`]); assert.strictEqual(excludes?.length, 3); assert.strictEqual(excludes[0], testDir); assert.strictEqual(excludes[1], join(testDir, 'foo', 'bar')); @@ -544,22 +544,22 @@ flakySuite('Recursive Watcher (parcel)', () => { // wrong casing is normalized for root if (!isLinux) { - excludes = service.toExcludePaths(testDir, [join(testDir.toUpperCase(), 'node_modules', '**')]); + excludes = watcher.toExcludePaths(testDir, [join(testDir.toUpperCase(), 'node_modules', '**')]); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, 'node_modules')); } // exclude ignored if not parent of watched dir - excludes = service.toExcludePaths(testDir, [join(dirname(testDir), 'node_modules', '**')]); + excludes = watcher.toExcludePaths(testDir, [join(dirname(testDir), 'node_modules', '**')]); assert.strictEqual(excludes, undefined); // relative paths - excludes = service.toExcludePaths(testDir, ['.']); + excludes = watcher.toExcludePaths(testDir, ['.']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], testDir); - excludes = service.toExcludePaths(testDir, ['foo', `bar${sep}`, join('foo', 'bar'), `${join('other', 'deep')}${sep}`]); + excludes = watcher.toExcludePaths(testDir, ['foo', `bar${sep}`, join('foo', 'bar'), `${join('other', 'deep')}${sep}`]); assert.strictEqual(excludes?.length, 4); assert.strictEqual(excludes[0], join(testDir, 'foo')); assert.strictEqual(excludes[1], join(testDir, 'bar')); @@ -568,51 +568,51 @@ flakySuite('Recursive Watcher (parcel)', () => { // simple globs (relative) - excludes = service.toExcludePaths(testDir, ['**']); + excludes = watcher.toExcludePaths(testDir, ['**']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], testDir); - excludes = service.toExcludePaths(testDir, ['**/**']); + excludes = watcher.toExcludePaths(testDir, ['**/**']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], testDir); - excludes = service.toExcludePaths(testDir, ['**\\**']); + excludes = watcher.toExcludePaths(testDir, ['**\\**']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], testDir); - excludes = service.toExcludePaths(testDir, ['**/node_modules/**']); + excludes = watcher.toExcludePaths(testDir, ['**/node_modules/**']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, 'node_modules')); - excludes = service.toExcludePaths(testDir, ['**/.git/objects/**']); + excludes = watcher.toExcludePaths(testDir, ['**/.git/objects/**']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, '.git', 'objects')); - excludes = service.toExcludePaths(testDir, ['**/node_modules']); + excludes = watcher.toExcludePaths(testDir, ['**/node_modules']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, 'node_modules')); - excludes = service.toExcludePaths(testDir, ['**/.git/objects']); + excludes = watcher.toExcludePaths(testDir, ['**/.git/objects']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, '.git', 'objects')); - excludes = service.toExcludePaths(testDir, ['node_modules/**']); + excludes = watcher.toExcludePaths(testDir, ['node_modules/**']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, 'node_modules')); - excludes = service.toExcludePaths(testDir, ['.git/objects/**']); + excludes = watcher.toExcludePaths(testDir, ['.git/objects/**']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, '.git', 'objects')); // simple globs (absolute) - excludes = service.toExcludePaths(testDir, [join(testDir, 'node_modules', '**')]); + excludes = watcher.toExcludePaths(testDir, [join(testDir, 'node_modules', '**')]); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, 'node_modules')); // Linux: more restrictive glob treatment if (isLinux) { - excludes = service.toExcludePaths(testDir, ['**/node_modules/*/**']); + excludes = watcher.toExcludePaths(testDir, ['**/node_modules/*/**']); assert.strictEqual(excludes?.length, 1); assert.strictEqual(excludes[0], join(testDir, 'node_modules')); } @@ -620,17 +620,17 @@ flakySuite('Recursive Watcher (parcel)', () => { // unsupported globs else { - excludes = service.toExcludePaths(testDir, ['**/node_modules/*/**']); + excludes = watcher.toExcludePaths(testDir, ['**/node_modules/*/**']); assert.strictEqual(excludes, undefined); } - excludes = service.toExcludePaths(testDir, ['**/*.js']); + excludes = watcher.toExcludePaths(testDir, ['**/*.js']); assert.strictEqual(excludes, undefined); - excludes = service.toExcludePaths(testDir, ['*.js']); + excludes = watcher.toExcludePaths(testDir, ['*.js']); assert.strictEqual(excludes, undefined); - excludes = service.toExcludePaths(testDir, ['*']); + excludes = watcher.toExcludePaths(testDir, ['*']); assert.strictEqual(excludes, undefined); }); }); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index a8bb4b1a4a755..6256c02727dd0 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -304,8 +304,6 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native colorScheme: IColorScheme; autoDetectHighContrast?: boolean; - legacyWatcher?: string; // TODO@bpasero remove me once watcher is settled - perfMarks: PerformanceMark[]; filesToWait?: IPathsToWaitFor; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 06a37000fab4f..98f3a9bdbff0c 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1293,7 +1293,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic os: { release: release(), hostname: hostname() }, zoomLevel: typeof windowConfig?.zoomLevel === 'number' ? windowConfig.zoomLevel : undefined, - legacyWatcher: this.configurationService.getValue('files.legacyWatcher'), autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true, accessibilitySupport: app.accessibilitySupportEnabled, colorScheme: { diff --git a/src/vs/server/remoteFileSystemProviderIpc.ts b/src/vs/server/remoteFileSystemProviderIpc.ts index a0da18bd4266f..e51c4e082108f 100644 --- a/src/vs/server/remoteFileSystemProviderIpc.ts +++ b/src/vs/server/remoteFileSystemProviderIpc.ts @@ -87,7 +87,7 @@ class SessionFileWatcher extends Disposable implements ISessionFileWatcher { })); this._register(this.fileWatcher.onDidChangeFile(events => localChangeEmitter.fire(events))); - this._register(this.fileWatcher.onDidErrorOccur(error => sessionEmitter.fire(error))); + this._register(this.fileWatcher.onDidWatchError(error => sessionEmitter.fire(error))); } private getWatcherOptions(): IWatcherOptions | undefined { diff --git a/src/vs/workbench/buildfile.desktop.js b/src/vs/workbench/buildfile.desktop.js index b694a4a92cbfd..2f59f700c84d8 100644 --- a/src/vs/workbench/buildfile.desktop.js +++ b/src/vs/workbench/buildfile.desktop.js @@ -12,8 +12,7 @@ exports.collectModules = function () { createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), - createModuleDescription('vs/platform/files/node/watcher/nsfw/watcherApp'), - createModuleDescription('vs/platform/files/node/watcher/parcel/watcherApp'), + createModuleDescription('vs/platform/files/node/watcher/parcel/parcelWatcherMain'), createModuleDescription('vs/platform/terminal/node/ptyHostMain'), diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index a64babf6f964d..d718488fbc645 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -257,22 +257,6 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('watcherInclude', "Configure extra paths to watch for changes inside the workspace. By default, all workspace folders will be watched recursively, except for folders that are symbolic links. You can explicitly add absolute or relative paths to support watching folders that are symbolic links. Relative paths will be resolved to an absolute path using the currently opened workspace."), 'scope': ConfigurationScope.RESOURCE }, - 'files.legacyWatcher': { - 'type': 'string', - 'enum': [ - 'on', - 'off', - 'default', - ], - 'markdownEnumDescriptions': [ - nls.localize('files.legacyWatcher.on', "Enable the legacy file watcher in case you see issues with the new file watcher."), - nls.localize('files.legacyWatcher.off', "Disable the legacy file watcher and enable the new file watcher to benefit from its capabilities."), - nls.localize('files.legacyWatcher.default', "The new file watcher will be enabled."), - ], - 'default': 'default', - 'description': nls.localize('legacyWatcher', "Controls the mechanism used for file watching. Only change this when you see issues related to file watching."), - 'scope': ConfigurationScope.APPLICATION - }, 'files.hotExit': hotExitConfiguration, 'files.defaultLanguage': { 'type': 'string', diff --git a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts index 68fc90c3a16cc..aa8dcea0024c6 100644 --- a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts @@ -41,7 +41,7 @@ export class WorkspaceWatcher extends Disposable { this._register(this.contextService.onDidChangeWorkspaceFolders(e => this.onDidChangeWorkspaceFolders(e))); this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState())); this._register(this.configurationService.onDidChangeConfiguration(e => this.onDidChangeConfiguration(e))); - this._register(this.fileService.onError(error => this.onError(error))); + this._register(this.fileService.onDidWatchError(error => this.onDidWatchError(error))); } private onDidChangeWorkspaceFolders(e: IWorkspaceFoldersChangeEvent): void { @@ -67,7 +67,7 @@ export class WorkspaceWatcher extends Disposable { } } - private onError(error: Error): void { + private onDidWatchError(error: Error): void { const msg = error.toString(); // Detect if we run into ENOSPC issues diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 9e3101852830a..a1884ae713680 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -26,7 +26,6 @@ interface IConfiguration extends IWindowsConfiguration { debug?: { console?: { wordWrap?: boolean } }; editor?: { accessibilitySupport?: 'on' | 'off' | 'auto' }; security?: { workspace?: { trust?: { enabled?: boolean } } }; - files?: { legacyWatcher?: string }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -38,7 +37,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private updateMode: string | undefined; private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; private workspaceTrustEnabled: boolean | undefined; - private legacyFileWatcher: string | undefined = undefined; constructor( @IHostService private readonly hostService: IHostService, @@ -100,12 +98,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo this.workspaceTrustEnabled = config.security.workspace.trust.enabled; changed = true; } - - // Legacy File Watcher - if (typeof config.files?.legacyWatcher === 'string' && config.files.legacyWatcher !== this.legacyFileWatcher) { - this.legacyFileWatcher = config.files.legacyWatcher; - changed = true; - } } // Notify only when changed and we are the focused window (avoids notification spam across windows) diff --git a/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts b/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts index 5542f71799811..ba5d448ea09bc 100644 --- a/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files/electron-sandbox/diskFileSystemProvider.ts @@ -13,8 +13,8 @@ import { ReadableStreamEvents } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; import { IPCFileSystemProvider } from 'vs/platform/files/common/ipcFileSystemProvider'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IDiskFileChange, ILogMessage, WatcherService } from 'vs/platform/files/common/watcher'; -import { ParcelFileWatcher } from 'vs/workbench/services/files/electron-sandbox/parcelWatcherService'; +import { IDiskFileChange, ILogMessage, AbstractRecursiveWatcherClient } from 'vs/platform/files/common/watcher'; +import { ParcelWatcherClient } from 'vs/workbench/services/files/electron-sandbox/parcelWatcherClient'; import { ILogService } from 'vs/platform/log/common/log'; import { ISharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService'; @@ -45,7 +45,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple // Forward events from the embedded provider this.provider.onDidChangeFile(e => this._onDidChangeFile.fire(e)); - this.provider.onDidErrorOccur(e => this._onDidErrorOccur.fire(e)); + this.provider.onDidWatchError(e => this._onDidWatchError.fire(e)); } //#region File Capabilities @@ -137,8 +137,8 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple onChange: (changes: IDiskFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean - ): WatcherService { - return new ParcelFileWatcher( + ): AbstractRecursiveWatcherClient { + return new ParcelWatcherClient( changes => onChange(changes), msg => onLogMessage(msg), verboseLogging, diff --git a/src/vs/workbench/services/files/electron-sandbox/parcelWatcherService.ts b/src/vs/workbench/services/files/electron-sandbox/parcelWatcherClient.ts similarity index 78% rename from src/vs/workbench/services/files/electron-sandbox/parcelWatcherService.ts rename to src/vs/workbench/services/files/electron-sandbox/parcelWatcherClient.ts index 1f3987192a43d..dd58e7b98cd3b 100644 --- a/src/vs/workbench/services/files/electron-sandbox/parcelWatcherService.ts +++ b/src/vs/workbench/services/files/electron-sandbox/parcelWatcherClient.ts @@ -5,10 +5,10 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { getDelayedChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; -import { AbstractWatcherService, IDiskFileChange, ILogMessage, IWatcherService } from 'vs/platform/files/common/watcher'; +import { AbstractRecursiveWatcherClient, IDiskFileChange, ILogMessage, IRecursiveWatcher } from 'vs/platform/files/common/watcher'; import { ISharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService'; -export class ParcelFileWatcher extends AbstractWatcherService { +export class ParcelWatcherClient extends AbstractRecursiveWatcherClient { constructor( onFileChanges: (changes: IDiskFileChange[]) => void, @@ -21,8 +21,8 @@ export class ParcelFileWatcher extends AbstractWatcherService { this.init(); } - protected override createService(disposables: DisposableStore): IWatcherService { - const service = ProxyChannel.toService(getDelayedChannel((async () => { + protected override createWatcher(disposables: DisposableStore): IRecursiveWatcher { + const watcher = ProxyChannel.toService(getDelayedChannel((async () => { // Acquire parcel watcher via shared process worker // @@ -33,8 +33,8 @@ export class ParcelFileWatcher extends AbstractWatcherService { // The shared process worker services ensures to terminate // the process automatically when the window closes or reloads. const { client, onDidTerminate } = await this.sharedProcessWorkerWorkbenchService.createWorker({ - moduleId: 'vs/platform/files/node/watcher/parcel/watcherApp', - type: 'watcherServiceParcelSharedProcess' + moduleId: 'vs/platform/files/node/watcher/parcel/parcelWatcherMain', + type: 'parcelWatcher' }); // React on unexpected termination of the watcher process @@ -54,8 +54,8 @@ export class ParcelFileWatcher extends AbstractWatcherService { // only seem to be happening when used from Electron, // not pure node.js. // https://github.com/microsoft/vscode/issues/136264 - disposables.add(toDisposable(() => service.stop())); + disposables.add(toDisposable(() => watcher.stop())); - return service; + return watcher; } } diff --git a/yarn.lock b/yarn.lock index 5bcbdcd1f10de..6729427e4bbea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6966,11 +6966,6 @@ node-addon-api@^3.2.1: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== -node-addon-api@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" - integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== - node-fetch@^2.6.0: version "2.6.6" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" @@ -10410,13 +10405,6 @@ vscode-nls-dev@^3.3.1: xml2js "^0.4.19" yargs "^13.2.4" -vscode-nsfw@2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-2.1.8.tgz#88f5e56b22b2fd0be582e73eb1158ea8257f6c6c" - integrity sha512-tFnxPIuM65czw/Kjz8KXD88fIJtnCjzQ0ighS0a1yasVv6jKkANAlGffiOitTLMkDjvFCY8OyP6xjarTkpu/VQ== - dependencies: - node-addon-api "^4.2.0" - vscode-oniguruma@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" From e1caacebce14ba66185bb64bcf4d9a7fbc457b2e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 10:55:33 +0100 Subject: [PATCH 0297/2210] backup - cancel pending operations on shutdown and handle cancellation better (#138055) --- .../workingCopy/common/workingCopyBackup.ts | 2 +- .../common/workingCopyBackupService.ts | 29 ++++-- .../common/workingCopyBackupTracker.ts | 68 ++++++++++---- .../workingCopyBackupTracker.ts | 90 +++++++++++-------- .../browser/workingCopyBackupTracker.test.ts | 11 ++- .../workingCopyBackupTracker.test.ts | 25 +++++- .../areas/workbench/data-migration.test.ts | 52 ++++++++--- 7 files changed, 199 insertions(+), 78 deletions(-) diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackup.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackup.ts index c84b1fdbafa1a..af3b551bded30 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackup.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackup.ts @@ -71,7 +71,7 @@ export interface IWorkingCopyBackupService { /** * Discards the working copy backup associated with the identifier if it exists. */ - discardBackup(identifier: IWorkingCopyIdentifier): Promise; + discardBackup(identifier: IWorkingCopyIdentifier, token?: CancellationToken): Promise; /** * Discards all working copy backups. diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts index 1f6015ede681d..165c14ebe870b 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -170,8 +170,8 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ return this.impl.backup(identifier, content, versionId, meta, token); } - discardBackup(identifier: IWorkingCopyIdentifier): Promise { - return this.impl.discardBackup(identifier); + discardBackup(identifier: IWorkingCopyIdentifier, token?: CancellationToken): Promise { + return this.impl.discardBackup(identifier, token); } discardBackups(filter?: { except: IWorkingCopyIdentifier[] }): Promise { @@ -322,7 +322,12 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac // Write backup via file service await this.fileService.writeFile(backupResource, backupBuffer); + // // Update model + // + // Note: not checking for cancellation here because a successful + // write into the backup file should be noted in the model to + // prevent the model being out of sync with the backup file model.add(backupResource, versionId, meta); }); } @@ -357,18 +362,32 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac } } - discardBackup(identifier: IWorkingCopyIdentifier): Promise { + discardBackup(identifier: IWorkingCopyIdentifier, token?: CancellationToken): Promise { const backupResource = this.toBackupResource(identifier); - return this.doDiscardBackup(backupResource); + return this.doDiscardBackup(backupResource, token); } - private async doDiscardBackup(backupResource: URI): Promise { + private async doDiscardBackup(backupResource: URI, token?: CancellationToken): Promise { const model = await this.ready; + if (token?.isCancellationRequested) { + return; + } return this.ioOperationQueues.queueFor(backupResource).queue(async () => { + if (token?.isCancellationRequested) { + return; + } + + // Delete backup file ignoring any file not found errors await this.deleteIgnoreFileNotFound(backupResource); + // + // Update model + // + // Note: not checking for cancellation here because a successful + // delete of the backup file should be noted in the model to + // prevent the model being out of sync with the backup file model.remove(backupResource); }); } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts index 36b3106b794ae..459f6ed9afde4 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts @@ -41,7 +41,9 @@ export abstract class WorkingCopyBackupTracker extends Disposable { super(); // Fill in initial dirty working copies - this.workingCopyService.dirtyWorkingCopies.forEach(workingCopy => this.onDidRegister(workingCopy)); + for (const workingCopy of this.workingCopyService.dirtyWorkingCopies) { + this.onDidRegister(workingCopy); + } this.registerListeners(); } @@ -69,8 +71,8 @@ export abstract class WorkingCopyBackupTracker extends Disposable { // content has been made before closing. private readonly mapWorkingCopyToContentVersion = new Map(); - // A map of scheduled pending backups for working copies - protected readonly pendingBackups = new Map(); + // A map of scheduled pending backup operations for working copies + protected readonly pendingBackupOperations = new Map(); // Delay creation of backups when content changes to avoid too much // load on the backup service when the user is typing into the editor @@ -127,7 +129,7 @@ export abstract class WorkingCopyBackupTracker extends Disposable { private scheduleBackup(workingCopy: IWorkingCopy): void { // Clear any running backup operation - this.cancelBackup(workingCopy); + this.cancelBackupOperation(workingCopy); this.logService.trace(`[backup tracker] scheduling backup`, workingCopy.resource.toString(true), workingCopy.typeId); @@ -158,18 +160,16 @@ export abstract class WorkingCopyBackupTracker extends Disposable { } } - if (cts.token.isCancellationRequested) { - return; + // Clear disposable unless we got canceled which would + // indicate another operation has started meanwhile + if (!cts.token.isCancellationRequested) { + this.pendingBackupOperations.delete(workingCopy); } - - // Clear disposable - this.pendingBackups.delete(workingCopy); - }, this.getBackupScheduleDelay(workingCopy)); // Keep in map for disposal as needed - this.pendingBackups.set(workingCopy, toDisposable(() => { - this.logService.trace(`[backup tracker] clearing pending backup`, workingCopy.resource.toString(true), workingCopy.typeId); + this.pendingBackupOperations.set(workingCopy, toDisposable(() => { + this.logService.trace(`[backup tracker] clearing pending backup creation`, workingCopy.resource.toString(true), workingCopy.typeId); cts.dispose(true); clearTimeout(handle); @@ -190,18 +190,48 @@ export abstract class WorkingCopyBackupTracker extends Disposable { } private discardBackup(workingCopy: IWorkingCopy): void { - this.logService.trace(`[backup tracker] discarding backup`, workingCopy.resource.toString(true), workingCopy.typeId); // Clear any running backup operation - this.cancelBackup(workingCopy); + this.cancelBackupOperation(workingCopy); + + // Schedule backup discard asap + const cts = new CancellationTokenSource(); + (async () => { + this.logService.trace(`[backup tracker] discarding backup`, workingCopy.resource.toString(true), workingCopy.typeId); + + // Discard backup + try { + await this.workingCopyBackupService.discardBackup(workingCopy, cts.token); + } catch (error) { + this.logService.error(error); + } + + // Clear disposable unless we got canceled which would + // indicate another operation has started meanwhile + if (!cts.token.isCancellationRequested) { + this.pendingBackupOperations.delete(workingCopy); + } + })(); + + // Keep in map for disposal as needed + this.pendingBackupOperations.set(workingCopy, toDisposable(() => { + this.logService.trace(`[backup tracker] clearing pending backup discard`, workingCopy.resource.toString(true), workingCopy.typeId); - // Forward to working copy backup service - this.workingCopyBackupService.discardBackup(workingCopy); + cts.dispose(true); + })); } - private cancelBackup(workingCopy: IWorkingCopy): void { - dispose(this.pendingBackups.get(workingCopy)); - this.pendingBackups.delete(workingCopy); + private cancelBackupOperation(workingCopy: IWorkingCopy): void { + dispose(this.pendingBackupOperations.get(workingCopy)); + this.pendingBackupOperations.delete(workingCopy); + } + + protected cancelBackupOperations(): void { + for (const [, disposable] of this.pendingBackupOperations) { + dispose(disposable); + } + + this.pendingBackupOperations.clear(); } protected abstract onBeforeShutdown(reason: ShutdownReason): boolean | Promise; diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index fb7cd5adc9568..9c2e2a054f244 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -50,6 +50,14 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { + // Important: we are about to shutdown and handle dirty working copies + // and backups. We do not want any pending backup ops to interfer with + // this because there is a risk of a backup being scheduled after we have + // acknowledged to shutdown and then might end up with partial backups + // written to disk, or even empty backups or deletes after writes. + // (https://github.com/microsoft/vscode/issues/138055) + this.cancelBackupOperations(); + // Dirty working copies need treatment on shutdown const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; if (dirtyWorkingCopies.length) { @@ -88,12 +96,13 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp private async handleDirtyBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[], reason: ShutdownReason): Promise { - // Trigger backup if configured + // Trigger backup if configured and enabled for shutdown reason let backups: IWorkingCopy[] = []; let backupError: Error | undefined = undefined; - if (this.filesConfigurationService.isHotExitEnabled) { + const backup = await this.shouldBackupBeforeShutdown(reason); + if (backup) { try { - const backupResult = await this.backupBeforeShutdown(dirtyWorkingCopies, reason); + const backupResult = await this.backupBeforeShutdown(dirtyWorkingCopies); backups = backupResult.backups; backupError = backupResult.error; @@ -137,67 +146,65 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp } } - private showErrorDialog(msg: string, workingCopies: readonly IWorkingCopy[], error?: Error): void { - const dirtyWorkingCopies = workingCopies.filter(workingCopy => workingCopy.isDirty()); - - const advice = localize('backupErrorDetails', "Try saving or reverting the editors with unsaved changes first and then try again."); - const detail = dirtyWorkingCopies.length - ? getFileNamesMessage(dirtyWorkingCopies.map(x => x.name)) + '\n' + advice - : advice; - - this.dialogService.show(Severity.Error, msg, undefined, { detail }); - - this.logService.error(error ? `[backup tracker] ${msg}: ${error}` : `[backup tracker] ${msg}`); - } - - private async backupBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[], reason: ShutdownReason): Promise<{ backups: IWorkingCopy[], error?: Error }> { + private async shouldBackupBeforeShutdown(reason: ShutdownReason): Promise { + let backup: boolean | undefined; + if (!this.filesConfigurationService.isHotExitEnabled) { + backup = false; // never backup when hot exit is disabled via settings + } else if (this.environmentService.isExtensionDevelopment) { + backup = true; // always backup closing extension development window without asking to speed up debugging + } else { - // When quit is requested skip the confirm callback and attempt to backup all workspaces. - // When quit is not requested the confirm callback should be shown when the window being - // closed is the only VS Code window open, except for on Mac where hot exit is only - // ever activated when quit is requested. + // When quit is requested skip the confirm callback and attempt to backup all workspaces. + // When quit is not requested the confirm callback should be shown when the window being + // closed is the only VS Code window open, except for on Mac where hot exit is only + // ever activated when quit is requested. - let doBackup: boolean | undefined; - if (this.environmentService.isExtensionDevelopment) { - doBackup = true; // always backup closing extension development window without asking to speed up debugging - } else { switch (reason) { case ShutdownReason.CLOSE: if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + backup = true; // backup if a folder is open and onExitAndWindowClose is configured } else if (await this.nativeHostService.getWindowCount() > 1 || isMacintosh) { - doBackup = false; // do not backup if a window is closed that does not cause quitting of the application + backup = false; // do not backup if a window is closed that does not cause quitting of the application } else { - doBackup = true; // backup if last window is closed on win/linux where the application quits right after + backup = true; // backup if last window is closed on win/linux where the application quits right after } break; case ShutdownReason.QUIT: - doBackup = true; // backup because next start we restore all backups + backup = true; // backup because next start we restore all backups break; case ShutdownReason.RELOAD: - doBackup = true; // backup because after window reload, backups restore + backup = true; // backup because after window reload, backups restore break; case ShutdownReason.LOAD: if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + backup = true; // backup if a folder is open and onExitAndWindowClose is configured } else { - doBackup = false; // do not backup because we are switching contexts + backup = false; // do not backup because we are switching contexts } break; } } - if (!doBackup) { - return { backups: [] }; - } + return backup; + } + + private showErrorDialog(msg: string, workingCopies: readonly IWorkingCopy[], error?: Error): void { + const dirtyWorkingCopies = workingCopies.filter(workingCopy => workingCopy.isDirty()); + + const advice = localize('backupErrorDetails', "Try saving or reverting the editors with unsaved changes first and then try again."); + const detail = dirtyWorkingCopies.length + ? getFileNamesMessage(dirtyWorkingCopies.map(x => x.name)) + '\n' + advice + : advice; + + this.dialogService.show(Severity.Error, msg, undefined, { detail }); - return this.doBackupBeforeShutdown(dirtyWorkingCopies); + this.logService.error(error ? `[backup tracker] ${msg}: ${error}` : `[backup tracker] ${msg}`); } - private async doBackupBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[]): Promise<{ backups: IWorkingCopy[], error?: Error }> { + private async backupBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[]): Promise<{ backups: IWorkingCopy[], error?: Error }> { const backups: IWorkingCopy[] = []; let error: Error | undefined = undefined; @@ -206,9 +213,9 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp // Perform a backup of all dirty working copies unless a backup already exists try { await Promises.settled(dirtyWorkingCopies.map(async workingCopy => { - const contentVersion = this.getContentVersion(workingCopy); // Backup exists + const contentVersion = this.getContentVersion(workingCopy); if (this.workingCopyBackupService.hasBackupSync(workingCopy, contentVersion)) { backups.push(workingCopy); } @@ -216,7 +223,14 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp // Backup does not exist else { const backup = await workingCopy.backup(token); + if (token.isCancellationRequested) { + return; + } + await this.workingCopyBackupService.backup(workingCopy, backup.content, contentVersion, backup.meta, token); + if (token.isCancellationRequested) { + return; + } backups.push(workingCopy); } diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts index baf3b728f1b6d..6e50280f54bad 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts @@ -17,7 +17,6 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { WorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/common/workingCopyBackupTracker'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { createEditorPart, InMemoryTestWorkingCopyBackupService, registerTestResourceEditor, TestServiceAccessor, toTypedWorkingCopyId, toUntypedWorkingCopyId, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; @@ -65,6 +64,8 @@ suite('WorkingCopyBackupTracker (browser)', function () { return 10; // Reduce timeout for tests } + get pendingBackupOperationCount(): number { return this.pendingBackupOperations.size; } + getUnrestoredBackups() { return this.unrestoredBackups; } @@ -85,7 +86,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { } } - async function createTracker(): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: WorkingCopyBackupTracker, workingCopyBackupService: InMemoryTestWorkingCopyBackupService, instantiationService: IInstantiationService, cleanup: () => void }> { + async function createTracker(): Promise<{ accessor: TestServiceAccessor, part: EditorPart, tracker: TestWorkingCopyBackupTracker, workingCopyBackupService: InMemoryTestWorkingCopyBackupService, instantiationService: IInstantiationService, cleanup: () => void }> { const disposables = new DisposableStore(); const workingCopyBackupService = new InMemoryTestWorkingCopyBackupService(); @@ -142,7 +143,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { }); test('Track backups (custom)', async function () { - const { accessor, cleanup, workingCopyBackupService } = await createTracker(); + const { accessor, tracker, cleanup, workingCopyBackupService } = await createTracker(); class TestBackupWorkingCopy extends TestWorkingCopy { @@ -166,15 +167,18 @@ suite('WorkingCopyBackupTracker (browser)', function () { // Normal customWorkingCopy.setDirty(true); + assert.strictEqual(tracker.pendingBackupOperationCount, 1); await workingCopyBackupService.joinBackupResource(); assert.strictEqual(workingCopyBackupService.hasBackupSync(customWorkingCopy), true); customWorkingCopy.setDirty(false); customWorkingCopy.setDirty(true); + assert.strictEqual(tracker.pendingBackupOperationCount, 1); await workingCopyBackupService.joinBackupResource(); assert.strictEqual(workingCopyBackupService.hasBackupSync(customWorkingCopy), true); customWorkingCopy.setDirty(false); + assert.strictEqual(tracker.pendingBackupOperationCount, 1); await workingCopyBackupService.joinDiscardBackup(); assert.strictEqual(workingCopyBackupService.hasBackupSync(customWorkingCopy), false); @@ -182,6 +186,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { customWorkingCopy.setDirty(true); await timeout(0); customWorkingCopy.setDirty(false); + assert.strictEqual(tracker.pendingBackupOperationCount, 1); await workingCopyBackupService.joinDiscardBackup(); assert.strictEqual(workingCopyBackupService.hasBackupSync(customWorkingCopy), false); diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index 539d615034db1..78da49044f7d4 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -75,10 +75,12 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { return super.whenReady; } + get pendingBackupOperationCount(): number { return this.pendingBackupOperations.size; } + override dispose() { super.dispose(); - for (const [_, disposable] of this.pendingBackups) { + for (const [_, disposable] of this.pendingBackupOperations) { disposable.dispose(); } } @@ -355,6 +357,27 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { await cleanup(); }); + test('onWillShutdown - pending backup operations canceled', async function () { + const { accessor, tracker, cleanup } = await createTracker(); + + const resource = toResource.call(this, '/path/index.txt'); + await accessor.editorService.openEditor({ resource, options: { pinned: true } }); + + const model = accessor.textFileService.files.get(resource); + + await model?.resolve(); + model?.textEditorModel?.setValue('foo'); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); + assert.strictEqual(tracker.pendingBackupOperationCount, 1); + + const event = new TestBeforeShutdownEvent(); + accessor.lifecycleService.fireBeforeShutdown(event); + + assert.strictEqual(tracker.pendingBackupOperationCount, 0); + + await cleanup(); + }); + suite('Hot Exit', () => { suite('"onExit" setting', () => { test('should hot exit on non-Mac (reason: CLOSE, windows: single, workspace)', function () { diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 518946c6469dc..8ee5865b7f3ad 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -15,7 +15,7 @@ export function setup(opts: ParsedArgs) { afterSuite(opts, () => app); - it(`verifies opened editors are restored`, async function () { + it('verifies opened editors are restored', async function () { app = await startApp(opts, this.defaultOptions); // Open 3 editors and pin 2 of them @@ -38,7 +38,15 @@ export function setup(opts: ParsedArgs) { app = undefined; }); - it(`verifies that 'hot exit' works for dirty files`, async function () { + it('verifies that "hot exit" works for dirty files (without delay)', function () { + return testHotExit.call(this, undefined); + }); + + it('verifies that "hot exit" works for dirty files (with delay)', function () { + return testHotExit.call(this, 2000); + }); + + async function testHotExit(restartDelay: number | undefined) { app = await startApp(opts, this.defaultOptions); await app.workbench.editors.newUntitledFile(); @@ -54,19 +62,27 @@ export function setup(opts: ParsedArgs) { await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType); await app.workbench.editors.waitForTab(readmeMd, true); + if (typeof restartDelay === 'number') { + // this is an OK use of a timeout in a smoke test + // we want to simulate a user having typed into + // the editor and pausing for a moment before + // terminating + await timeout(restartDelay); + } + await app.restart(); await app.workbench.editors.waitForTab(readmeMd, true); await app.workbench.quickaccess.openFile(readmeMd); - await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); + await app.workbench.editor.waitForEditorContents(readmeMd, contents => contents.indexOf(textToType) > -1); await app.workbench.editors.waitForTab(untitled, true); await app.workbench.quickaccess.openFile(untitled, textToTypeInUntitled); - await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); + await app.workbench.editor.waitForEditorContents(untitled, contents => contents.indexOf(textToTypeInUntitled) > -1); await app.stop(); app = undefined; - }); + } }); describe('Data Migration (stable -> insiders)', () => { @@ -76,7 +92,7 @@ export function setup(opts: ParsedArgs) { afterSuite(opts, () => insidersApp ?? stableApp, async () => stableApp?.stop()); - it(`verifies opened editors are restored`, async function () { + it('verifies opened editors are restored', async function () { const stableCodePath = opts['stable-build']; if (!stableCodePath || opts.remote) { this.skip(); @@ -126,7 +142,15 @@ export function setup(opts: ParsedArgs) { insidersApp = undefined; }); - it(`verifies that 'hot exit' works for dirty files`, async function () { + it('verifies that "hot exit" works for dirty files (without delay)', async function () { + return testHotExit.call(this, undefined); + }); + + it('verifies that "hot exit" works for dirty files (with delay)', async function () { + return testHotExit.call(this, 2000); + }); + + async function testHotExit(restartDelay: number | undefined) { const stableCodePath = opts['stable-build']; if (!stableCodePath || opts.remote) { this.skip(); @@ -155,7 +179,13 @@ export function setup(opts: ParsedArgs) { await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType); await stableApp.workbench.editors.waitForTab(readmeMd, true); - await timeout(2000); // TODO@bpasero https://github.com/microsoft/vscode/issues/138055 + if (typeof restartDelay === 'number') { + // this is an OK use of a timeout in a smoke test + // we want to simulate a user having typed into + // the editor and pausing for a moment before + // terminating + await timeout(restartDelay); + } await stableApp.stop(); stableApp = undefined; @@ -168,14 +198,14 @@ export function setup(opts: ParsedArgs) { await insidersApp.workbench.editors.waitForTab(readmeMd, true); await insidersApp.workbench.quickaccess.openFile(readmeMd); - await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); + await insidersApp.workbench.editor.waitForEditorContents(readmeMd, contents => contents.indexOf(textToType) > -1); await insidersApp.workbench.editors.waitForTab(untitled, true); await insidersApp.workbench.quickaccess.openFile(untitled, textToTypeInUntitled); - await insidersApp.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); + await insidersApp.workbench.editor.waitForEditorContents(untitled, contents => contents.indexOf(textToTypeInUntitled) > -1); await insidersApp.stop(); insidersApp = undefined; - }); + } }); } From fe227dacb71c40c38e91a5f59a407f6c37def728 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 11:16:57 +0100 Subject: [PATCH 0298/2210] lifecycle - introduce and adopt `finalVeto` for backups (#138055) --- src/vs/base/browser/ui/dialog/dialog.ts | 12 +- src/vs/base/common/async.ts | 70 +++++++++++- src/vs/base/test/common/async.test.ts | 45 ++++++-- src/vs/platform/progress/common/progress.ts | 1 + src/vs/workbench/electron-sandbox/window.ts | 61 +++++++++- .../lifecycle/browser/lifecycleService.ts | 35 +++--- .../services/lifecycle/common/lifecycle.ts | 40 +++++++ .../lifecycle/common/lifecycleService.ts | 7 +- .../electron-sandbox/lifecycleService.ts | 104 ++++++++++-------- .../progress/browser/progressService.ts | 6 +- .../textfile/common/textFileEditorModel.ts | 4 +- .../browser/workingCopyBackupTracker.ts | 2 +- .../common/storedFileWorkingCopy.ts | 4 +- .../common/workingCopyBackupService.ts | 12 ++ .../common/workingCopyBackupTracker.ts | 20 +++- .../workingCopyBackupService.ts | 15 ++- .../workingCopyBackupTracker.ts | 2 +- .../workingCopyBackupService.test.ts | 50 ++++++++- .../workingCopyBackupTracker.test.ts | 4 + .../test/browser/workbenchTestServices.ts | 23 ++-- ...ta-migration.test.ts => data-loss.test.ts} | 20 +++- test/smoke/src/main.ts | 4 +- 22 files changed, 432 insertions(+), 109 deletions(-) rename test/smoke/src/areas/workbench/{data-migration.test.ts => data-loss.test.ts} (92%) diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index a3dd82374005a..e2511966a85f9 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -37,6 +37,7 @@ export interface IDialogOptions { readonly icon?: Codicon; readonly buttonDetails?: string[]; readonly disableCloseAction?: boolean; + readonly disableDefaultAction?: boolean; } export interface IDialogResult { @@ -91,7 +92,13 @@ export class Dialog extends Disposable { this.element.tabIndex = -1; hide(this.element); - this.buttons = Array.isArray(buttons) && buttons.length ? buttons : [nls.localize('ok', "OK")]; // If no button is provided, default to OK + if (Array.isArray(buttons) && buttons.length > 0) { + this.buttons = buttons; + } else if (!this.options.disableDefaultAction) { + this.buttons = [nls.localize('ok', "OK")]; + } else { + this.buttons = []; + } const buttonsRowElement = this.element.appendChild($('.dialog-buttons-row')); this.buttonsContainer = buttonsRowElement.appendChild($('.dialog-buttons')); @@ -483,6 +490,9 @@ export class Dialog extends Disposable { private rearrangeButtons(buttons: Array, cancelId: number | undefined): ButtonMapEntry[] { const buttonMap: ButtonMapEntry[] = []; + if (buttons.length === 0) { + return buttonMap; + } // Maps each button to its current label and old index so that when we move them around it's not a problem buttons.forEach((button, index) => { diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 86852857b09f8..44cfa7feea8e8 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -515,11 +515,18 @@ interface ILimitedTaskFactory { e: (error?: unknown) => void; } +export interface ILimiter { + + readonly size: number; + + queue(factory: ITask>): Promise; +} + /** * A helper to queue N promises and run them all with a max degree of parallelism. The helper * ensures that at any time no more than M promises are running at the same time. */ -export class Limiter { +export class Limiter implements ILimiter{ private _size = 0; private runningPromises: number; @@ -596,7 +603,30 @@ export class ResourceQueue implements IDisposable { private readonly queues = new Map>(); - queueFor(resource: URI, extUri: IExtUri = defaultExtUri): Queue { + private readonly drainers = new Set>(); + + async whenDrained(): Promise { + if (this.isDrained()) { + return; + } + + const promise = new DeferredPromise(); + this.drainers.add(promise); + + return promise.p; + } + + private isDrained(): boolean { + for (const [, queue] of this.queues) { + if (queue.size > 0) { + return false; + } + } + + return true; + } + + queueFor(resource: URI, extUri: IExtUri = defaultExtUri): ILimiter { const key = extUri.getComparisonKey(resource); let queue = this.queues.get(key); @@ -605,6 +635,7 @@ export class ResourceQueue implements IDisposable { Event.once(queue.onFinished)(() => { queue?.dispose(); this.queues.delete(key); + this.onDidQueueFinish(); }); this.queues.set(key, queue); @@ -613,9 +644,36 @@ export class ResourceQueue implements IDisposable { return queue; } + private onDidQueueFinish(): void { + if (!this.isDrained()) { + return; // not done yet + } + + this.releaseDrainers(); + } + + private releaseDrainers(): void { + for (const drainer of this.drainers) { + drainer.complete(); + } + + this.drainers.clear(); + } + dispose(): void { - this.queues.forEach(queue => queue.dispose()); + for (const [, queue] of this.queues) { + queue.dispose(); + } + this.queues.clear(); + + // Even though we might still have pending + // tasks queued, after the queues have been + // disposed, we can no longer track them, so + // we release drainers to prevent hanging + // promises when the resource queue is being + // disposed. + this.releaseDrainers(); } } @@ -1408,7 +1466,7 @@ export class AsyncIterableObject implements AsyncIterable { public static merge(iterables: AsyncIterable[]): AsyncIterableObject { return new AsyncIterableObject(async (emitter) => { await Promise.all(iterables.map(async (iterable) => { - for await(const item of iterable) { + for await (const item of iterable) { emitter.emitOne(item); } })); @@ -1469,7 +1527,7 @@ export class AsyncIterableObject implements AsyncIterable { public static map(iterable: AsyncIterable, mapFn: (item: T) => R): AsyncIterableObject { return new AsyncIterableObject(async (emitter) => { - for await(const item of iterable) { + for await (const item of iterable) { emitter.emitOne(mapFn(item)); } }); @@ -1481,7 +1539,7 @@ export class AsyncIterableObject implements AsyncIterable { public static filter(iterable: AsyncIterable, filterFn: (item: T) => boolean): AsyncIterableObject { return new AsyncIterableObject(async (emitter) => { - for await(const item of iterable) { + for await (const item of iterable) { if (filterFn(item)) { emitter.emitOne(item); } diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index f3ac328c5295c..894f83b3b0472 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -511,11 +511,11 @@ suite('Async', () => { }); }); - test('events', function () { + test('events', async function () { let queue = new async.Queue(); let finished = false; - const onFinished = Event.toPromise(queue.onFinished); + const onFinished = Event.toPromise(queue.onFinished).then(() => finished = true); let res: number[] = []; @@ -534,32 +534,55 @@ suite('Async', () => { }); }); - return onFinished; + await onFinished; + assert.ok(finished); }); }); suite('ResourceQueue', () => { - test('simple', function () { + test('simple', async function () { let queue = new async.ResourceQueue(); + await queue.whenDrained(); // returns immediately since empty + const r1Queue = queue.queueFor(URI.file('/some/path')); - r1Queue.onFinished(() => console.log('DONE')); + await queue.whenDrained(); // returns immediately since empty const r2Queue = queue.queueFor(URI.file('/some/other/path')); + await queue.whenDrained(); // returns immediately since empty + assert.ok(r1Queue); assert.ok(r2Queue); assert.strictEqual(r1Queue, queue.queueFor(URI.file('/some/path'))); // same queue returned - let syncPromiseFactory = () => Promise.resolve(undefined); + // schedule some work + const w1 = new async.DeferredPromise(); + r1Queue.queue(() => w1.p); - r1Queue.queue(syncPromiseFactory); + let drained = false; + queue.whenDrained().then(() => drained = true); + assert.strictEqual(drained, false); + await w1.complete(); + await async.timeout(0); + assert.strictEqual(drained, true); - return new Promise(c => setTimeout(() => c(), 0)).then(() => { - const r1Queue2 = queue.queueFor(URI.file('/some/path')); - assert.notStrictEqual(r1Queue, r1Queue2); // previous one got disposed after finishing - }); + const r1Queue2 = queue.queueFor(URI.file('/some/path')); + assert.notStrictEqual(r1Queue, r1Queue2); // previous one got disposed after finishing + + // schedule some work + const w2 = new async.DeferredPromise(); + const w3 = new async.DeferredPromise(); + r1Queue.queue(() => w2.p); + r2Queue.queue(() => w3.p); + + drained = false; + queue.whenDrained().then(() => drained = true); + + queue.dispose(); + await async.timeout(0); + assert.strictEqual(drained, true); }); }); diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 71678fc8f7c90..5334563dfa03d 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -69,6 +69,7 @@ export interface IProgressNotificationOptions extends IProgressOptions { export interface IProgressDialogOptions extends IProgressOptions { readonly delay?: number; readonly detail?: string; + readonly sticky?: boolean; } export interface IProgressWindowOptions extends IProgressOptions { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 150e2192154b3..71ae726d12972 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -27,7 +27,7 @@ import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, MenuRegist import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { LifecyclePhase, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { LifecyclePhase, ILifecycleService, WillShutdownEvent, ShutdownReason, BeforeShutdownErrorEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; @@ -61,6 +61,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { whenEditorClosed } from 'vs/workbench/browser/editor'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; export class NativeWindow extends Disposable { @@ -111,7 +113,8 @@ export class NativeWindow extends Disposable { @IStorageService private readonly storageService: IStorageService, @ILogService private readonly logService: ILogService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ISharedProcessService private readonly sharedProcessService: ISharedProcessService + @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, + @IProgressService private readonly progressService: IProgressService ) { super(); @@ -312,6 +315,60 @@ export class NativeWindow extends Disposable { // Detect panel position to determine minimum width this._register(this.layoutService.onDidChangePanelPosition(pos => this.onDidChangePanelPosition(positionFromString(pos)))); this.onDidChangePanelPosition(this.layoutService.getPanelPosition()); + + // Lifecycle + this._register(this.lifecycleService.onBeforeShutdownError(e => this.onBeforeShutdownError(e))); + this._register(this.lifecycleService.onWillShutdown((e) => this.onWillShutdown(e))); + } + + private onBeforeShutdownError({ error, reason }: BeforeShutdownErrorEvent): void { + let message: string; + switch (reason) { + case ShutdownReason.CLOSE: + message = localize('shutdownErrorClose', "An unexpected error prevented the window to close."); + break; + case ShutdownReason.QUIT: + message = localize('shutdownErrorQuit', "An unexpected error prevented the application to quit."); + break; + case ShutdownReason.RELOAD: + message = localize('shutdownErrorReload', "An unexpected error prevented the window to reload."); + break; + case ShutdownReason.LOAD: + message = localize('shutdownErrorLoad', "An unexpected error prevented to change the workspace of the window."); + break; + } + + this.dialogService.show(Severity.Error, message, undefined, { + detail: localize('shutdownErrorDetail', "Error: {0}", toErrorMessage(error)) + }); + } + + private onWillShutdown({ reason }: WillShutdownEvent): void { + let title: string; + switch (reason) { + case ShutdownReason.CLOSE: + title = localize('shutdownTitleClose', "Closing the window is taking longer than expected..."); + break; + case ShutdownReason.QUIT: + title = localize('shutdownTitleQuit', "Quitting the application is taking longer than expected..."); + break; + case ShutdownReason.RELOAD: + title = localize('shutdownTitleReload', "Reloading the window is taking longer than expected..."); + break; + case ShutdownReason.LOAD: + title = localize('shutdownTitleLoad', "Changing the workspace is taking longer than expected..."); + break; + } + + this.progressService.withProgress({ + location: ProgressLocation.Dialog, // use a dialog to prevent the user from making any more interactions now + delay: 800, // delay notification so that it only appears when operation takes a long time + cancellable: false, // do not allow to cancel + sticky: true, // do not allow to dismiss + title + }, () => { + return Event.toPromise(this.lifecycleService.onDidShutdown); // dismiss this dialog when we actually shutdown + }); } private onWindowResize(e: UIEvent, retry: boolean): void { diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index b22cc8fa308be..5f8339d3d0111 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -115,22 +115,31 @@ export class BrowserLifecycleService extends AbstractLifecycleService { let veto = false; - // Before Shutdown - this._onBeforeShutdown.fire({ - veto(value, id) { - if (typeof vetoShutdown === 'function') { - if (value instanceof Promise) { - logService.error(`[lifecycle] Long running operations before shutdown are unsupported in the web (id: ${id})`); + function handleVeto(vetoResult: boolean | Promise, id: string) { + if (typeof vetoShutdown !== 'function') { + return; // veto handling disabled + } - value = true; // implicitly vetos since we cannot handle promises in web - } + if (vetoResult instanceof Promise) { + logService.error(`[lifecycle] Long running operations before shutdown are unsupported in the web (id: ${id})`); - if (value === true) { - logService.info(`[lifecycle]: Unload was prevented (id: ${id})`); + veto = true; // implicitly vetos since we cannot handle promises in web + } + + if (vetoResult === true) { + logService.info(`[lifecycle]: Unload was prevented (id: ${id})`); - veto = true; - } - } + veto = true; + } + } + + // Before Shutdown + this._onBeforeShutdown.fire({ + veto(value, id) { + handleVeto(value, id); + }, + finalVeto(valueFn, id) { + handleVeto(valueFn(), id); // in browser, trigger instantly because we do not support async anyway }, reason: ShutdownReason.QUIT }); diff --git a/src/vs/workbench/services/lifecycle/common/lifecycle.ts b/src/vs/workbench/services/lifecycle/common/lifecycle.ts index ce06f71369761..82e8905e18108 100644 --- a/src/vs/workbench/services/lifecycle/common/lifecycle.ts +++ b/src/vs/workbench/services/lifecycle/common/lifecycle.ts @@ -34,6 +34,36 @@ export interface BeforeShutdownEvent { readonly reason: ShutdownReason; } +export interface InternalBeforeShutdownEvent extends BeforeShutdownEvent { + + /** + * Allows to set a veto operation to run after all other + * vetos have been handled from the `BeforeShutdownEvent` + * + * This method is hidden from the API because it is intended + * to be only used once internally. + */ + finalVeto(vetoFn: () => boolean | Promise, id: string): void; +} + +/** + * An event that signals an error happened during `onBeforeShutdown` veto handling. + * In this case the shutdown operation will not proceed because this is an unexpected + * condition that is treated like a veto. + */ +export interface BeforeShutdownErrorEvent { + + /** + * The reason why the application is shutting down. + */ + readonly reason: ShutdownReason; + + /** + * The error that happened during shutdown handling. + */ + readonly error: Error; +} + /** * An event that is send out when the window closes. Clients have a chance to join the closing * by providing a promise from the join method. Returning a promise is useful in cases of long @@ -154,6 +184,15 @@ export interface ILifecycleService { */ readonly onBeforeShutdown: Event; + /** + * Fired when an error happened during `onBeforeShutdown` veto handling. + * In this case the shutdown operation will not proceed because this is + * an unexpected condition that is treated like a veto. + * + * The event carries a shutdown reason that indicates how the shutdown was triggered. + */ + readonly onBeforeShutdownError: Event; + /** * Fired when no client is preventing the shutdown from happening (from `onBeforeShutdown`). * @@ -193,6 +232,7 @@ export const NullLifecycleService: ILifecycleService = { _serviceBrand: undefined, onBeforeShutdown: Event.None, + onBeforeShutdownError: Event.None, onWillShutdown: Event.None, onDidShutdown: Event.None, diff --git a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts index 62f9022883827..08de22c35d53d 100644 --- a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts @@ -6,7 +6,7 @@ import { Emitter } from 'vs/base/common/event'; import { Barrier } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ILifecycleService, BeforeShutdownEvent, WillShutdownEvent, StartupKind, LifecyclePhase, LifecyclePhaseToString, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ILifecycleService, WillShutdownEvent, StartupKind, LifecyclePhase, LifecyclePhaseToString, ShutdownReason, BeforeShutdownErrorEvent, InternalBeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { mark } from 'vs/base/common/performance'; import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; @@ -17,7 +17,7 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi declare readonly _serviceBrand: undefined; - protected readonly _onBeforeShutdown = this._register(new Emitter()); + protected readonly _onBeforeShutdown = this._register(new Emitter()); readonly onBeforeShutdown = this._onBeforeShutdown.event; protected readonly _onWillShutdown = this._register(new Emitter()); @@ -26,6 +26,9 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi protected readonly _onDidShutdown = this._register(new Emitter()); readonly onDidShutdown = this._onDidShutdown.event; + protected readonly _onBeforeShutdownError = this._register(new Emitter()); + readonly onBeforeShutdownError = this._onBeforeShutdownError.event; + private _startupKind: StartupKind; get startupKind(): StartupKind { return this._startupKind; } diff --git a/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts index b3ae6ed65a199..c4ace02d569cd 100644 --- a/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts @@ -3,20 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { ILogService } from 'vs/platform/log/common/log'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { AbstractLifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycleService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import Severity from 'vs/base/common/severity'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { Promises, disposableTimeout } from 'vs/base/common/async'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; export class NativeLifecycleService extends AbstractLifecycleService { @@ -24,7 +20,6 @@ export class NativeLifecycleService extends AbstractLifecycleService { private static readonly WILL_SHUTDOWN_WARNING_DELAY = 5000; constructor( - @INotificationService private readonly notificationService: INotificationService, @INativeHostService private readonly nativeHostService: INativeHostService, @IStorageService storageService: IStorageService, @ILogService logService: ILogService @@ -38,22 +33,26 @@ export class NativeLifecycleService extends AbstractLifecycleService { const windowId = this.nativeHostService.windowId; // Main side indicates that window is about to unload, check for vetos - ipcRenderer.on('vscode:onBeforeUnload', (event: unknown, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => { + ipcRenderer.on('vscode:onBeforeUnload', async (event: unknown, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => { this.logService.trace(`[lifecycle] onBeforeUnload (reason: ${reply.reason})`); // trigger onBeforeShutdown events and veto collecting - this.handleBeforeShutdown(reply.reason).then(veto => { - if (veto) { - this.logService.trace('[lifecycle] onBeforeUnload prevented via veto'); + const veto = await this.handleBeforeShutdown(reply.reason); - ipcRenderer.send(reply.cancelChannel, windowId); - } else { - this.logService.trace('[lifecycle] onBeforeUnload continues without veto'); + // veto: cancel unload + if (veto) { + this.logService.trace('[lifecycle] onBeforeUnload prevented via veto'); - this.shutdownReason = reply.reason; - ipcRenderer.send(reply.okChannel, windowId); - } - }); + ipcRenderer.send(reply.cancelChannel, windowId); + } + + // no veto: allow unload + else { + this.logService.trace('[lifecycle] onBeforeUnload continues without veto'); + + this.shutdownReason = reply.reason; + ipcRenderer.send(reply.okChannel, windowId); + } }); // Main side indicates that we will indeed shutdown @@ -73,9 +72,14 @@ export class NativeLifecycleService extends AbstractLifecycleService { private async handleBeforeShutdown(reason: ShutdownReason): Promise { const logService = this.logService; + const vetos: (boolean | Promise)[] = []; const pendingVetos = new Set(); + let finalVeto: (() => boolean | Promise) | undefined = undefined; + let finalVetoId: string | undefined = undefined; + + // before-shutdown event with veto support this._onBeforeShutdown.fire({ veto(value, id) { vetos.push(value); @@ -95,6 +99,14 @@ export class NativeLifecycleService extends AbstractLifecycleService { }).finally(() => pendingVetos.delete(id)); } }, + finalVeto(value, id) { + if (!finalVeto) { + finalVeto = value; + finalVetoId = id; + } else { + throw new Error(`[lifecycle]: Final veto is already defined (id: ${id})`); + } + }, reason }); @@ -103,12 +115,40 @@ export class NativeLifecycleService extends AbstractLifecycleService { }, NativeLifecycleService.BEFORE_SHUTDOWN_WARNING_DELAY); try { - return await handleVetos(vetos, error => this.onShutdownError(reason, error)); + + // First: run list of vetos in parallel + let veto = await handleVetos(vetos, error => this.handleBeforeShutdownError(error, reason)); + if (veto) { + return veto; + } + + // Second: run the final veto if defined + if (finalVeto) { + try { + pendingVetos.add(finalVetoId as unknown as string); + veto = await (finalVeto as () => Promise)(); + if (veto) { + logService.info(`[lifecycle]: Shutdown was prevented by final veto (id: ${finalVetoId})`); + } + } catch (error) { + veto = true; // treat error as veto + + this.handleBeforeShutdownError(error, reason); + } + } + + return veto; } finally { longRunningBeforeShutdownWarning.dispose(); } } + private handleBeforeShutdownError(error: Error, reason: ShutdownReason): void { + this.logService.error(`[lifecycle]: Error during before-shutdown phase (error: ${toErrorMessage(error)})`); + + this._onBeforeShutdownError.fire({ reason, error }); + } + private async handleWillShutdown(reason: ShutdownReason): Promise { const joiners: Promise[] = []; const pendingJoiners = new Set(); @@ -131,38 +171,12 @@ export class NativeLifecycleService extends AbstractLifecycleService { try { await Promises.settled(joiners); } catch (error) { - this.onShutdownError(reason, error); + this.logService.error(`[lifecycle]: Error during will-shutdown phase (error: ${toErrorMessage(error)})`); // this error will not prevent the shutdown } finally { longRunningWillShutdownWarning.dispose(); } } - private onShutdownError(reason: ShutdownReason, error: Error): void { - let message: string; - switch (reason) { - case ShutdownReason.CLOSE: - message = localize('errorClose', "An unexpected error was thrown while attempting to close the window ({0}).", toErrorMessage(error)); - break; - case ShutdownReason.QUIT: - message = localize('errorQuit', "An unexpected error was thrown while attempting to quit the application ({0}).", toErrorMessage(error)); - break; - case ShutdownReason.RELOAD: - message = localize('errorReload', "An unexpected error was thrown while attempting to reload the window ({0}).", toErrorMessage(error)); - break; - case ShutdownReason.LOAD: - message = localize('errorLoad', "An unexpected error was thrown while attempting to change the workspace of the window ({0}).", toErrorMessage(error)); - break; - } - - this.notificationService.notify({ - severity: Severity.Error, - message, - sticky: true - }); - - onUnexpectedError(error); - } - shutdown(): Promise { return this.nativeHostService.closeWindow(); } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 0957d9529c62b..ee2120d232839 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -512,7 +512,9 @@ export class ProgressService extends Disposable implements IProgressService { const createDialog = (message: string) => { const buttons = options.buttons || []; - buttons.push(options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")); + if (!options.sticky) { + buttons.push(options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")); + } dialog = new Dialog( this.layoutService.container, @@ -522,6 +524,8 @@ export class ProgressService extends Disposable implements IProgressService { type: 'pending', detail: options.detail, cancelId: buttons.length - 1, + disableCloseAction: options.sticky, + disableDefaultAction: options.sticky, keyEventProcessor: (event: StandardKeyboardEvent) => { const resolved = this.keybindingService.softDispatch(event, this.layoutService.container); if (resolved?.commandId) { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 7abefe152d861..b24fadf363dbc 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -969,8 +969,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - joinState(state: TextFileEditorModelState.PENDING_SAVE): Promise { - return this.saveSequentializer.pending ?? Promise.resolve(); + async joinState(state: TextFileEditorModelState.PENDING_SAVE): Promise { + return this.saveSequentializer.pending; } override getMode(this: IResolvedTextFileEditorModel): string; diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts index 7a0ea34b52430..54d2fd61b2e34 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts @@ -29,7 +29,7 @@ export class BrowserWorkingCopyBackupTracker extends WorkingCopyBackupTracker im super(workingCopyBackupService, workingCopyService, logService, lifecycleService, filesConfigurationService, workingCopyEditorService, editorService, editorGroupService); } - protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { + protected onBeforeShutdown(reason: ShutdownReason): boolean { // Web: we cannot perform long running in the shutdown phase // As such we need to check sync if there are any dirty working diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts index 6c4c5503c37cb..dfa24fe54f0bd 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts @@ -1164,8 +1164,8 @@ export class StoredFileWorkingCopy extend } } - joinState(state: StoredFileWorkingCopyState.PENDING_SAVE): Promise { - return this.saveSequentializer.pending ?? Promise.resolve(); + async joinState(state: StoredFileWorkingCopyState.PENDING_SAVE): Promise { + return this.saveSequentializer.pending; } //#endregion diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts index 165c14ebe870b..056f9354ec722 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -189,6 +189,10 @@ export abstract class WorkingCopyBackupService implements IWorkingCopyBackupServ toBackupResource(identifier: IWorkingCopyIdentifier): URI { return this.impl.toBackupResource(identifier); } + + joinBackups(): Promise { + return this.impl.joinBackups(); + } } class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBackupService { @@ -540,6 +544,10 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac toBackupResource(identifier: IWorkingCopyIdentifier): URI { return joinPath(this.backupWorkspaceHome, identifier.resource.scheme, hashIdentifier(identifier)); } + + joinBackups(): Promise { + return this.ioOperationQueues.whenDrained(); + } } export class InMemoryWorkingCopyBackupService implements IWorkingCopyBackupService { @@ -608,6 +616,10 @@ export class InMemoryWorkingCopyBackupService implements IWorkingCopyBackupServi toBackupResource(identifier: IWorkingCopyIdentifier): URI { return URI.from({ scheme: Schemas.inMemory, path: hashIdentifier(identifier) }); } + + async joinBackups(): Promise { + return; + } } /* diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts index 459f6ed9afde4..2766c23d3c7e6 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts @@ -8,7 +8,7 @@ import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/l import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IWorkingCopy, IWorkingCopyIdentifier, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { ILogService } from 'vs/platform/log/common/log'; -import { ShutdownReason, ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ShutdownReason, ILifecycleService, LifecyclePhase, InternalBeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; @@ -56,13 +56,27 @@ export abstract class WorkingCopyBackupTracker extends Disposable { this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onDidChangeDirty(workingCopy))); this._register(this.workingCopyService.onDidChangeContent(workingCopy => this.onDidChangeContent(workingCopy))); - // Lifecycle (handled in subclasses) - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason), 'veto.backups')); + // Lifecycle + this.lifecycleService.onBeforeShutdown(event => (event as InternalBeforeShutdownEvent).finalVeto(() => this.onBeforeShutdown(event.reason), 'veto.backups')); + this.lifecycleService.onWillShutdown(() => this.onWillShutdown()); // Once a handler registers, restore backups this._register(this.workingCopyEditorService.onDidRegisterHandler(handler => this.restoreBackups(handler))); } + private onWillShutdown(): void { + + // Here we know that we will shutdown. Any backup operation that is + // already scheduled or being scheduled from this moment on runs + // at the risk of corrupting a backup because the backup operation + // might terminate at any given time now. As such, we need to disable + // this tracker from performing more backups by cancelling pending + // operations and disposing our listeners. + + this.cancelBackupOperations(); + this.dispose(); + } + //#region Backup Creator diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts index 758acf322c80a..ee86a36748579 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts @@ -12,7 +12,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { NativeWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker'; export class NativeWorkingCopyBackupService extends WorkingCopyBackupService { @@ -20,9 +20,20 @@ export class NativeWorkingCopyBackupService extends WorkingCopyBackupService { constructor( @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IFileService fileService: IFileService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @ILifecycleService private readonly lifecycleService: ILifecycleService ) { super(environmentService.configuration.backupPath ? URI.file(environmentService.configuration.backupPath).with({ scheme: environmentService.userRoamingDataHome.scheme }) : undefined, fileService, logService); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Lifecycle: ensure to prolong the shutdown for as long + // as pending backup operations have not finished yet. + // Otherwise, we risk writing partial backups to disk. + this.lifecycleService.onWillShutdown(event => event.join(this.joinBackups(), 'join.workingCopyBackups')); } } diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index 9c2e2a054f244..911ae767e91b8 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -48,7 +48,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp super(workingCopyBackupService, workingCopyService, logService, lifecycleService, filesConfigurationService, workingCopyEditorService, editorService, editorGroupService); } - protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { + protected onBeforeShutdown(reason: ShutdownReason): Promise { // Important: we are about to shutdown and handle dirty working copies // and backups. We do not want any pending backup ops to interfer with diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts index b47f98ae0de55..b0f1e49df89d2 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts @@ -28,7 +28,7 @@ import { NativeWorkingCopyBackupService } from 'vs/workbench/services/workingCop import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { bufferToReadable, bufferToStream, streamToBuffer, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; -import { TestProductService, toTypedWorkingCopyId, toUntypedWorkingCopyId } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLifecycleService, TestProductService, toTypedWorkingCopyId, toUntypedWorkingCopyId } from 'vs/workbench/test/browser/workbenchTestServices'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { consumeStream } from 'vs/base/common/stream'; @@ -55,7 +55,8 @@ export class NodeTestWorkingCopyBackupService extends NativeWorkingCopyBackupSer const environmentService = new TestWorkbenchEnvironmentService(testDir, workspaceBackupPath); const logService = new NullLogService(); const fileService = new FileService(logService); - super(environmentService, fileService, logService); + const lifecycleService = new TestLifecycleService(); + super(environmentService, fileService, logService, lifecycleService); this.diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, this.diskFileSystemProvider); @@ -328,6 +329,30 @@ flakySuite('WorkingCopyBackupService', () => { return `${identifier.resource.toString()} ${JSON.stringify({ ...meta, typeId: identifier.typeId })}\n${content}`; } + test('joining', async () => { + let backupJoined = false; + const joinBackupsPromise = service.joinBackups(); + joinBackupsPromise.then(() => backupJoined = true); + await joinBackupsPromise; + assert.strictEqual(backupJoined, true); + + backupJoined = false; + service.joinBackups().then(() => backupJoined = true); + + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + const backupPromise = service.backup(identifier); + assert.strictEqual(backupJoined, false); + await backupPromise; + assert.strictEqual(backupJoined, true); + + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier)); + assert.ok(service.hasBackupSync(identifier)); + }); + test('no text', async () => { const identifier = toUntypedWorkingCopyId(fooFile); const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); @@ -531,6 +556,27 @@ flakySuite('WorkingCopyBackupService', () => { suite('discardBackup', () => { + test('joining', async () => { + const identifier = toUntypedWorkingCopyId(fooFile); + const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + + await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.ok(service.hasBackupSync(identifier)); + + let backupJoined = false; + service.joinBackups().then(() => backupJoined = true); + + const discardBackupPromise = service.discardBackup(identifier); + assert.strictEqual(backupJoined, false); + await discardBackupPromise; + assert.strictEqual(backupJoined, true); + + assert.strictEqual(existsSync(backupPath), false); + assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 0); + assert.ok(!service.hasBackupSync(identifier)); + }); + test('text file', async () => { const identifier = toUntypedWorkingCopyId(fooFile); const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index 78da49044f7d4..840d98f2c0926 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -354,6 +354,9 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { const veto = await event.value; assert.ok(veto); + const finalVeto = await event.finalValue?.(); + assert.ok(finalVeto); // assert the tracker uses the internal finalVeto API + await cleanup(); }); @@ -514,6 +517,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { accessor.lifecycleService.fireBeforeShutdown(event); const veto = await event.value; + assert.ok(typeof event.finalValue === 'function'); // assert the tracker uses the internal finalVeto API assert.strictEqual(accessor.workingCopyBackupService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel assert.strictEqual(veto, shouldVeto); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index eff636e571d4c..759c958bfddd2 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -21,7 +21,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IEditorOptions, IResourceEditorInput, IEditorModel, IResourceEditorInputIdentifier, ITextResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ILifecycleService, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent, BeforeShutdownErrorEvent, InternalBeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { FileOperationEvent, IFileService, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, ICreateFileOptions, IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, IFileStatWithMetadata, IResolveMetadataFileOptions, IWriteFileOptions, IReadFileOptions, IFileContent, IFileStreamContent, FileOperationError, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, IReadFileStreamOptions, IFileSystemProviderCapabilitiesChangeEvent, IRawFileChangesEvent } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -1209,14 +1209,17 @@ export class TestLifecycleService implements ILifecycleService { phase!: LifecyclePhase; startupKind!: StartupKind; - private readonly _onBeforeShutdown = new Emitter(); - get onBeforeShutdown(): Event { return this._onBeforeShutdown.event; } + private readonly _onBeforeShutdown = new Emitter(); + get onBeforeShutdown(): Event { return this._onBeforeShutdown.event; } + + private readonly _onBeforeShutdownError = new Emitter(); + get onBeforeShutdownError(): Event { return this._onBeforeShutdownError.event; } private readonly _onWillShutdown = new Emitter(); get onWillShutdown(): Event { return this._onWillShutdown.event; } - private readonly _onShutdown = new Emitter(); - get onDidShutdown(): Event { return this._onShutdown.event; } + private readonly _onDidShutdown = new Emitter(); + get onDidShutdown(): Event { return this._onDidShutdown.event; } async when(): Promise { } @@ -1233,7 +1236,7 @@ export class TestLifecycleService implements ILifecycleService { }); } - fireBeforeShutdown(event: BeforeShutdownEvent): void { this._onBeforeShutdown.fire(event); } + fireBeforeShutdown(event: InternalBeforeShutdownEvent): void { this._onBeforeShutdown.fire(event); } fireWillShutdown(event: WillShutdownEvent): void { this._onWillShutdown.fire(event); } @@ -1242,14 +1245,20 @@ export class TestLifecycleService implements ILifecycleService { } } -export class TestBeforeShutdownEvent implements BeforeShutdownEvent { +export class TestBeforeShutdownEvent implements InternalBeforeShutdownEvent { value: boolean | Promise | undefined; + finalValue: (() => boolean | Promise) | undefined; reason = ShutdownReason.CLOSE; veto(value: boolean | Promise): void { this.value = value; } + + finalVeto(vetoFn: () => boolean | Promise): void { + this.value = vetoFn(); + this.finalValue = vetoFn; + } } export class TestWillShutdownEvent implements WillShutdownEvent { diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts similarity index 92% rename from test/smoke/src/areas/workbench/data-migration.test.ts rename to test/smoke/src/areas/workbench/data-loss.test.ts index 8ee5865b7f3ad..c315c37ef9cad 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Application, ApplicationOptions, Quality } from '../../../../automation'; +import { Application, ApplicationOptions, Quality } from '../../../../automation/out'; import { ParsedArgs } from 'minimist'; import { afterSuite, getRandomUserDataDir, startApp, timeout } from '../../utils'; export function setup(opts: ParsedArgs) { - describe('Data Migration (insiders -> insiders)', () => { + describe('Data Loss (insiders -> insiders)', () => { let app: Application | undefined = undefined; @@ -46,9 +46,17 @@ export function setup(opts: ParsedArgs) { return testHotExit.call(this, 2000); }); - async function testHotExit(restartDelay: number | undefined) { + it('verifies that auto save triggers on shutdown', function () { + return testHotExit.call(this, undefined, true); + }); + + async function testHotExit(restartDelay: number | undefined, autoSave: boolean | undefined) { app = await startApp(opts, this.defaultOptions); + if (autoSave) { + await app.workbench.settingsEditor.addUserSetting('files.autoSave', '"afterDelay"'); + } + await app.workbench.editors.newUntitledFile(); const untitled = 'Untitled-1'; @@ -60,7 +68,7 @@ export function setup(opts: ParsedArgs) { const textToType = 'Hello, Code'; await app.workbench.quickaccess.openFile(readmeMd); await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType); - await app.workbench.editors.waitForTab(readmeMd, true); + await app.workbench.editors.waitForTab(readmeMd, !autoSave); if (typeof restartDelay === 'number') { // this is an OK use of a timeout in a smoke test @@ -72,7 +80,7 @@ export function setup(opts: ParsedArgs) { await app.restart(); - await app.workbench.editors.waitForTab(readmeMd, true); + await app.workbench.editors.waitForTab(readmeMd, !autoSave); await app.workbench.quickaccess.openFile(readmeMd); await app.workbench.editor.waitForEditorContents(readmeMd, contents => contents.indexOf(textToType) > -1); @@ -85,7 +93,7 @@ export function setup(opts: ParsedArgs) { } }); - describe('Data Migration (stable -> insiders)', () => { + describe('Data Loss (stable -> insiders)', () => { let insidersApp: Application | undefined = undefined; let stableApp: Application | undefined = undefined; diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 8f23a483e15fa..b1364814a616f 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -17,7 +17,7 @@ import * as vscodetest from 'vscode-test'; import fetch from 'node-fetch'; import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger } from '../../automation'; -import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; +import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; import { setup as setupPreferencesTests } from './areas/preferences/preferences.test'; import { setup as setupSearchTests } from './areas/search/search.test'; import { setup as setupNotebookTests } from './areas/notebook/notebook.test'; @@ -350,7 +350,7 @@ after(async function () { }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { - if (!opts.web) { setupDataMigrationTests(opts); } + if (!opts.web) { setupDataLossTests(opts); } if (!opts.web) { setupPreferencesTests(opts); } setupSearchTests(opts); setupNotebookTests(opts); From 11d343aef8d5d93562ba8ec208cc33caa7828c7c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 11:19:30 +0100 Subject: [PATCH 0299/2210] storage - add `whenInit` property to join init phase --- src/vs/platform/storage/electron-main/storageMain.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/platform/storage/electron-main/storageMain.ts b/src/vs/platform/storage/electron-main/storageMain.ts index e82e328647e02..3a8c84c3d148a 100644 --- a/src/vs/platform/storage/electron-main/storageMain.ts +++ b/src/vs/platform/storage/electron-main/storageMain.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DeferredPromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { join } from 'vs/base/common/path'; @@ -45,6 +46,12 @@ export interface IStorageMain extends IDisposable { */ readonly items: Map; + /** + * Allows to join on the `init` call having completed + * to be able to safely use the storage. + */ + readonly whenInit: Promise; + /** * Required call to ensure the service can be used. */ @@ -90,6 +97,9 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain { private initializePromise: Promise | undefined = undefined; + private readonly whenInitPromise = new DeferredPromise(); + readonly whenInit = this.whenInitPromise.p; + constructor( protected readonly logService: ILogService ) { @@ -125,6 +135,8 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain { } } catch (error) { this.logService.error(`StorageMain#initialize(): Unable to init storage due to ${error}`); + } finally { + this.whenInitPromise.complete(); } })(); } From 72d8d114ab5d46a1e15ab1ee8a0b88e271407545 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 6 Dec 2021 12:20:00 +0100 Subject: [PATCH 0300/2210] revert changes to User agent header --- .../extensionManagement/common/extensionGalleryService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6cea0d617fea2..f547bdb49cf03 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -1035,8 +1035,7 @@ export async function resolveMarketplaceHeaders(version: string, productService: } | undefined): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, - 'X-Market-Client-Version': version, - 'User-Agent': `VSCode (${productService.nameShort})`, + 'User-Agent': `VSCode ${version}` }; const uuid = await getServiceMachineId(environmentService, fileService, storageService); if (supportsTelemetry(productService, environmentService) && getTelemetryLevel(configurationService) === TelemetryLevel.USAGE) { From 61291e514579a5bed886a5c833a1afde4fe811f8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 6 Dec 2021 12:20:00 +0100 Subject: [PATCH 0301/2210] revert changes to User agent header --- .../extensionManagement/common/extensionGalleryService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6cea0d617fea2..f547bdb49cf03 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -1035,8 +1035,7 @@ export async function resolveMarketplaceHeaders(version: string, productService: } | undefined): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, - 'X-Market-Client-Version': version, - 'User-Agent': `VSCode (${productService.nameShort})`, + 'User-Agent': `VSCode ${version}` }; const uuid = await getServiceMachineId(environmentService, fileService, storageService); if (supportsTelemetry(productService, environmentService) && getTelemetryLevel(configurationService) === TelemetryLevel.USAGE) { From 22f7085a3245848a770bd8fdd68b6be2f4ef66d8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 6 Dec 2021 12:29:40 +0100 Subject: [PATCH 0302/2210] don't trigger suggest when composing characters, fixes https://github.com/microsoft/vscode/issues/132939 and fixes https://github.com/microsoft/vscode/issues/83428 --- src/vs/editor/contrib/suggest/suggestModel.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 9cda841e03f9f..af2703631f1d3 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -185,9 +185,6 @@ export class SuggestModel implements IDisposable { this._updateTriggerCharacters(); this._updateActiveSuggestSession(); })); - this._toDispose.add(this._editor.onDidChangeCursorSelection(e => { - this._onCursorChange(e); - })); let editorIsComposing = false; this._toDispose.add(this._editor.onDidCompositionStart(() => { @@ -197,6 +194,12 @@ export class SuggestModel implements IDisposable { editorIsComposing = false; this._onCompositionEnd(); })); + this._toDispose.add(this._editor.onDidChangeCursorSelection(e => { + // only trigger suggest when the editor isn't composing a character + if (!editorIsComposing) { + this._onCursorChange(e); + } + })); this._toDispose.add(this._editor.onDidChangeModelContent(() => { // only filter completions when the editor isn't composing a character // allow-any-unicode-next-line From e8e0fb874ddb9dcbce39a6f8189ebc25e18021fd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 13:44:01 +0100 Subject: [PATCH 0303/2210] smoke - disable test that is flaky still --- test/smoke/src/areas/workbench/data-loss.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index c315c37ef9cad..0d7a46e99eee5 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -150,7 +150,7 @@ export function setup(opts: ParsedArgs) { insidersApp = undefined; }); - it('verifies that "hot exit" works for dirty files (without delay)', async function () { + it.skip('verifies that "hot exit" works for dirty files (without delay)', async function () { // TODO@bpasero enable test once 1.63 shipped return testHotExit.call(this, undefined); }); From 4498f73ed7112f7a4d0f646819a9d383cc16386d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 6 Dec 2021 13:56:02 +0100 Subject: [PATCH 0304/2210] fix https://github.com/microsoft/vscode/issues/138502 --- .../contrib/documentSymbols/outlineModel.ts | 6 ++-- .../api/extHostLanguageFeatures.test.ts | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/documentSymbols/outlineModel.ts b/src/vs/editor/contrib/documentSymbols/outlineModel.ts index 216e948fff351..f56f706248630 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineModel.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineModel.ts @@ -10,7 +10,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { LRUCache } from 'vs/base/common/map'; import { commonPrefixLength } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { IPosition } from 'vs/editor/common/core/position'; +import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { DocumentSymbol, DocumentSymbolProvider, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; @@ -460,7 +460,9 @@ export class OutlineModel extends TreeElement { const roots = this.getTopLevelSymbols(); const bucket: DocumentSymbol[] = []; OutlineModel._flattenDocumentSymbols(bucket, roots, ''); - return bucket.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + return bucket.sort((a, b) => + Position.compare(Range.getStartPosition(a.range), Range.getStartPosition(b.range)) || Position.compare(Range.getEndPosition(b.range), Range.getEndPosition(a.range)) + ); } private static _flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void { diff --git a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts index af5bd88241b12..d26a6fbf6b08b 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -174,6 +174,36 @@ suite('ExtHostLanguageFeatures', function () { assert.deepStrictEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); }); + test('Quick Outline uses a not ideal sorting, #138502', async function () { + const symbols = [ + { name: 'containers', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 26 } }, + { name: 'container 0', range: { startLineNumber: 2, startColumn: 5, endLineNumber: 5, endColumn: 1 } }, + { name: 'name', range: { startLineNumber: 2, startColumn: 5, endLineNumber: 2, endColumn: 16 } }, + { name: 'ports', range: { startLineNumber: 3, startColumn: 5, endLineNumber: 5, endColumn: 1 } }, + { name: 'ports 0', range: { startLineNumber: 4, startColumn: 9, endLineNumber: 4, endColumn: 26 } }, + { name: 'containerPort', range: { startLineNumber: 4, startColumn: 9, endLineNumber: 4, endColumn: 26 } } + ]; + + extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, { + provideDocumentSymbols: (doc, token): any => { + return symbols.map(s => { + return new types.SymbolInformation( + s.name, + types.SymbolKind.Object, + new types.Range(s.range.startLineNumber - 1, s.range.startColumn - 1, s.range.endLineNumber - 1, s.range.endColumn - 1) + ); + }); + } + }); + + await rpcProtocol.sync(); + + const value = await getDocumentSymbols(model, true, CancellationToken.None); + + assert.strictEqual(value.length, 6); + assert.deepStrictEqual(value.map(s => s.name), ['containers', 'container 0', 'name', 'ports', 'ports 0', 'containerPort']); + }); + // --- code lens test('CodeLens, evil provider', async () => { From 2085b19a51fcab47f4991f51ae8f9eda7923c1d7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 14:08:56 +0100 Subject: [PATCH 0305/2210] smoke - allow 30s max for deleting test path (#137725) --- test/smoke/src/main.ts | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index b1364814a616f..9c2027eafb7d4 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -16,6 +16,7 @@ import { ncp } from 'ncp'; import * as vscodetest from 'vscode-test'; import fetch from 'node-fetch'; import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger } from '../../automation'; +import { timeout } from './utils'; import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; import { setup as setupPreferencesTests } from './areas/preferences/preferences.test'; @@ -342,9 +343,34 @@ after(async function () { } try { - await new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 10 }, error => error ? reject(error) : resolve())); + // TODO@tyriar TODO@meganrogge lately deleting the test root + // folder results in timeouts of 60s or EPERM issues which + // seems to indicate that a process (terminal?) holds onto a + // folder within. + // + // Workarounds pushed for mitigation + // - do not end up with mocha timeout errors after 60s by limiting + // this operation to at maximum 30s + // - do not end up with a failing `after` call when deletion failed + // + // Refs: https://github.com/microsoft/vscode/issues/137725 + let deleted = false; + await Promise.race([ + new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 10 }, error => { + if (error) { + reject(error); + } else { + deleted = true; + resolve(); + } + })), + timeout(30000).then(() => { + if (!deleted) { + throw new Error('giving up after 30s'); + } + }) + ]); } catch (error) { - // TODO@tyriar TODO@meganrogge https://github.com/microsoft/vscode/issues/137725 console.error(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`); } }); From c515bf8c5e6c9d04aa4571d621f01fa26e19ba65 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 6 Dec 2021 15:00:15 +0100 Subject: [PATCH 0306/2210] make sure theme is loaded when restoring it. Fixes #138422 --- .../workbench/services/themes/browser/workbenchThemeService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 08073f0ea6538..3f95ee603d0fd 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -481,6 +481,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (settingId !== this.currentColorTheme.settingsId) { await this.internalSetColorTheme(theme.id, undefined); } else if (theme !== this.currentColorTheme) { + await theme.ensureLoaded(this.extensionResourceLoaderService); theme.setCustomizations(this.settings); await this.applyTheme(theme, undefined, true); } @@ -656,6 +657,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (settingId !== this.currentFileIconTheme.settingsId) { await this.internalSetFileIconTheme(theme.id, undefined); } else if (theme !== this.currentFileIconTheme) { + await theme.ensureLoaded(this.extensionResourceLoaderService); this.applyAndSetFileIconTheme(theme, true); } return true; @@ -761,6 +763,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (settingId !== this.currentProductIconTheme.settingsId) { await this.internalSetProductIconTheme(theme.id, undefined); } else if (theme !== this.currentProductIconTheme) { + await theme.ensureLoaded(this.extensionResourceLoaderService, this.logService); this.applyAndSetProductIconTheme(theme, true); } return true; From 110345a97b55bdd697e49f027e550ed1489bc280 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Dec 2021 06:35:57 -0800 Subject: [PATCH 0307/2210] Try removing timeout in checkWindowReady Part of #137847 --- test/automation/src/application.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index d6e024dd54857..7f61b6643cd5c 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -125,9 +125,5 @@ export class Application { if (this.remote) { await this.code.waitForTextContent('.monaco-workbench .statusbar-item[id="status.host"]', ' TestResolver', undefined, 2000); } - - // wait a bit, since focus might be stolen off widgets - // as soon as they open (e.g. quick access) - await new Promise(c => setTimeout(c, 1000)); } } From d3815b7112f8b5f51c85bc76b596546aeaeb70d6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 6 Dec 2021 16:05:08 +0100 Subject: [PATCH 0308/2210] wrap web worker inside iframe when running in desktop --- .../browser/webWorkerExtensionHost.ts | 5 +- .../fileWebWorkerExtensionHostIframe.html | 65 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/services/extensions/worker/fileWebWorkerExtensionHostIframe.html diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index a3ac30b427ed5..617c53b956aa5 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -30,6 +30,7 @@ import { canceled, onUnexpectedError } from 'vs/base/common/errors'; import { Barrier } from 'vs/base/common/async'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { NewWorkerMessage, TerminateWorkerMessage } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol'; +import { FileAccess } from 'vs/base/common/network'; export interface IWebWorkerExtensionHostInitData { readonly autoStart: boolean; @@ -141,7 +142,8 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost this._protocolPromise = this._startOutsideIframe(); } } else { - this._protocolPromise = this._startOutsideIframe(); + const fileExtensionHostIframeSrc = FileAccess.asBrowserUri('../worker/fileWebWorkerExtensionHostIframe.html', require); + this._protocolPromise = this._startInsideIframe(`${fileExtensionHostIframeSrc.toString(true)}?`); } this._protocolPromise.then(protocol => this._protocol = protocol); } @@ -158,6 +160,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost const vscodeWebWorkerExtHostId = generateUuid(); iframe.setAttribute('src', `${webWorkerExtensionHostIframeSrc}&vscodeWebWorkerExtHostId=${vscodeWebWorkerExtHostId}`); + // iframe.setAttribute('src', `${webWorkerExtensionHostIframeSrc}`); const barrier = new Barrier(); let port!: MessagePort; diff --git a/src/vs/workbench/services/extensions/worker/fileWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/fileWebWorkerExtensionHostIframe.html new file mode 100644 index 0000000000000..c7cc0c58aab21 --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/fileWebWorkerExtensionHostIframe.html @@ -0,0 +1,65 @@ + + + + + + + + + From 95a9378519a945f147d5e8547b62cdf0b91bb2c8 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 6 Dec 2021 16:56:12 +0100 Subject: [PATCH 0309/2210] [json] add 'Clear schema cache' command. Fixes #138524 --- .../client/src/jsonClient.ts | 16 +++++++++++-- .../client/src/node/jsonClientMain.ts | 16 ++++++++++--- .../client/src/node/schemaCache.ts | 23 ++++++++++++++++++- .../json-language-features/package.json | 10 ++++++-- .../json-language-features/package.nls.json | 3 ++- .../server/src/jsonServer.ts | 16 ++++++++++--- 6 files changed, 72 insertions(+), 12 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 11c8c615299af..01604b92d4e3c 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -27,7 +27,7 @@ namespace VSCodeContentRequest { } namespace SchemaContentChangeNotification { - export const type: NotificationType = new NotificationType('json/schemaContent'); + export const type: NotificationType = new NotificationType('json/schemaContent'); } namespace ForceValidateRequest { @@ -101,6 +101,7 @@ export interface Runtime { export interface SchemaRequestService { getContent(uri: string): Promise; + clearCache?(): Promise; } export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server'); @@ -111,7 +112,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua let rangeFormatting: Disposable | undefined = undefined; - const documentSelector = ['json', 'jsonc']; const schemaResolutionErrorStatusBarItem = window.createStatusBarItem('status.json.resolveError', StatusBarAlignment.Right, 0); @@ -122,6 +122,16 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua const fileSchemaErrors = new Map(); let schemaDownloadEnabled = true; + let isClientReady = false; + + commands.registerCommand('json.clearCache', async () => { + if (isClientReady && runtime.schemaRequests.clearCache) { + const cachedSchemas = await runtime.schemaRequests.clearCache(); + await client.sendNotification(SchemaContentChangeNotification.type, cachedSchemas); + } + window.showInformationMessage(localize('json.clearCache.completed', "JSON schema cache cleared.")); + }); + // Options to control the language client const clientOptions: LanguageClientOptions = { // Register the server for json documents @@ -209,6 +219,8 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua const disposable = client.start(); toDispose.push(disposable); client.onReady().then(() => { + isClientReady = true; + const schemaDocuments: { [uri: string]: boolean } = {}; // handle content request diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index fd8b34ad355fc..b5ec23c29a9e5 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -101,14 +101,23 @@ const retryTimeoutInHours = 2 * 24; // 2 days async function getSchemaRequestService(context: ExtensionContext, log: Log): Promise { let cache: JSONSchemaCache | undefined = undefined; const globalStorage = context.globalStorageUri; + + let clearCache: (() => Promise) | undefined; if (globalStorage.scheme === 'file') { const schemaCacheLocation = path.join(globalStorage.fsPath, 'json-schema-cache'); await fs.mkdir(schemaCacheLocation, { recursive: true }); - cache = new JSONSchemaCache(schemaCacheLocation, context.globalState); - log.trace(`[json schema cache] initial state: ${JSON.stringify(cache.getCacheInfo(), null, ' ')}`); + const schemaCache = new JSONSchemaCache(schemaCacheLocation, context.globalState); + log.trace(`[json schema cache] initial state: ${JSON.stringify(schemaCache.getCacheInfo(), null, ' ')}`); + cache = schemaCache; + clearCache = async () => { + const cachedSchemas = await schemaCache.clearCache(); + log.trace(`[json schema cache] cache cleared. Previously cached schemas: ${cachedSchemas.join(', ')}`); + return cachedSchemas; + }; } + const isXHRResponse = (error: any): error is XHRResponse => typeof error?.status === 'number'; const request = async (uri: string, etag?: string): Promise => { @@ -172,6 +181,7 @@ async function getSchemaRequestService(context: ExtensionContext, log: Log): Pro } } return request(uri, cache?.getETag(uri)); - } + }, + clearCache }; } diff --git a/extensions/json-language-features/client/src/node/schemaCache.ts b/extensions/json-language-features/client/src/node/schemaCache.ts index 7f44498603d99..ad14e3228a9c7 100644 --- a/extensions/json-language-features/client/src/node/schemaCache.ts +++ b/extensions/json-language-features/client/src/node/schemaCache.ts @@ -21,7 +21,7 @@ interface CacheInfo { const MEMENTO_KEY = 'json-schema-cache'; export class JSONSchemaCache { - private readonly cacheInfo: CacheInfo; + private cacheInfo: CacheInfo; constructor(private readonly schemaCacheLocation: string, private readonly globalState: Memento) { const infos = globalState.get(MEMENTO_KEY, {}) as CacheInfo; @@ -120,6 +120,27 @@ export class JSONSchemaCache { // ignore } } + + public async clearCache(): Promise { + const uris = Object.keys(this.cacheInfo); + try { + const files = await fs.readdir(this.schemaCacheLocation); + for (const file of files) { + try { + await fs.unlink(path.join(this.schemaCacheLocation, file)); + } catch (_e) { + // ignore + } + } + } catch (e) { + // ignore + } finally { + + this.cacheInfo = {}; + await this.updateMemento(); + } + return uris; + } } function getCacheFileName(uri: string): string { return `${createHash('MD5').update(uri).digest('hex')}.schema.json`; diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 38e9fbc6e3247..2cbf0d2232335 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -12,7 +12,8 @@ "icon": "icons/json.png", "activationEvents": [ "onLanguage:json", - "onLanguage:jsonc" + "onLanguage:jsonc", + "onCommand:json.clearCache" ], "main": "./client/out/node/jsonClientMain", "browser": "./client/dist/browser/jsonClientMain", @@ -133,7 +134,12 @@ "fileMatch": "*.schema.json", "url": "http://json-schema.org/draft-07/schema#" } - ] + ], + "commands": [{ + "command": "json.clearCache", + "title": "%json.command.clearCache%", + "category": "JSON" + }] }, "dependencies": { "request-light": "^0.5.5", diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index 42100504378de..f83dd588ecdfd 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -14,5 +14,6 @@ "json.clickToRetry": "Click to retry.", "json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).", "json.maxItemsExceededInformation.desc": "Show notification when exceeding the maximum number of outline symbols and folding regions.", - "json.enableSchemaDownload.desc": "When enabled, JSON schemas can be fetched from http and https locations." + "json.enableSchemaDownload.desc": "When enabled, JSON schemas can be fetched from http and https locations.", + "json.command.clearCache": "Clear schema cache" } diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index 142594e0c3cbf..612c5a22bda06 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -27,7 +27,7 @@ namespace VSCodeContentRequest { } namespace SchemaContentChangeNotification { - export const type: NotificationType = new NotificationType('json/schemaContent'); + export const type: NotificationType = new NotificationType('json/schemaContent'); } namespace ResultLimitReachedNotification { @@ -264,8 +264,18 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); // A schema has changed - connection.onNotification(SchemaContentChangeNotification.type, uri => { - if (languageService.resetSchema(uri)) { + connection.onNotification(SchemaContentChangeNotification.type, uriOrUris => { + let needsRevalidation = false; + if (Array.isArray(uriOrUris)) { + for (const uri of uriOrUris) { + if (languageService.resetSchema(uri)) { + needsRevalidation = true; + } + } + } else { + needsRevalidation = languageService.resetSchema(uriOrUris); + } + if (needsRevalidation) { for (const doc of documents.all()) { triggerValidation(doc); } From e5c76ddbbd8a2bb676e839a5ba35960671b3ddf1 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 6 Dec 2021 10:02:32 -0600 Subject: [PATCH 0310/2210] set shellType when it changes, fix #138388 (#138418) --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9c62e2572940e..623c78d0956ca 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1102,6 +1102,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { case ProcessPropertyType.ResolvedShellLaunchConfig: this._setResolvedShellLaunchConfig(value); break; + case ProcessPropertyType.ShellType: + this.setShellType(value); + break; case ProcessPropertyType.HasChildProcesses: this._onDidChangeHasChildProcesses.fire(value); break; From b03cce63f0b2e00ced4e14a360f326236a761a78 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 6 Dec 2021 10:03:29 -0600 Subject: [PATCH 0311/2210] when view visibility changes, don't assume the panel should be focused (#138419) --- .../contrib/terminal/browser/terminalView.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 596a05c71e96d..8c82e626e80ab 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -145,12 +145,10 @@ export class TerminalViewPane extends ViewPane { } else { this._onDidChangeViewWelcomeState.fire(); } - if (!this._terminalService.activeInstance?.shellLaunchConfig.extHostTerminalId) { - // showPanel is already called with !preserveFocus - // when extension host terminals are created - this._terminalGroupService.showPanel(true); - } - + // we don't know here whether or not it should be focused, so + // defer focusing the panel to the focus() call + // to prevent overriding preserveFocus for extensions + this._terminalGroupService.showPanel(false); if (hadTerminals) { this._terminalGroupService.activeGroup?.setVisible(visible); } @@ -247,17 +245,13 @@ export class TerminalViewPane extends ViewPane { // Only focus the terminal if the activeElement has not changed since focus() was called // TODO hack if (document.activeElement === activeElement) { - this._focus(); + this._terminalGroupService.showPanel(true); } })); return; } - this._focus(); - } - - private _focus() { - this._terminalService.activeInstance?.focusWhenReady(); + this._terminalGroupService.showPanel(true); } override shouldShowWelcome(): boolean { From 2f898c4c98a9a4fb7bfef4fc5682e03a4a360359 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 6 Dec 2021 09:28:21 +0100 Subject: [PATCH 0312/2210] Fixes #138157: Remove option `_enableBuiltinExtensions` --- .../browser/builtinExtensionsScannerService.ts | 16 +--------------- src/vs/workbench/workbench.web.api.ts | 7 ------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts index b89968418ab6b..5eb9d09bf2edb 100644 --- a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts @@ -8,7 +8,6 @@ import { isWeb } from 'vs/base/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { URI } from 'vs/base/common/uri'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { FileAccess } from 'vs/base/common/network'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; @@ -32,7 +31,7 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne @IUriIdentityService uriIdentityService: IUriIdentityService, ) { if (isWeb) { - const builtinExtensionsServiceUrl = this._getBuiltinExtensionsUrl(environmentService); + const builtinExtensionsServiceUrl = FileAccess.asBrowserUri('../../../../../../extensions', require); if (builtinExtensionsServiceUrl) { let bundledExtensions: IBundledExtension[] = []; @@ -63,19 +62,6 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne } } - private _getBuiltinExtensionsUrl(environmentService: IWorkbenchEnvironmentService): URI | undefined { - let enableBuiltinExtensions: boolean; - if (environmentService.options && typeof environmentService.options._enableBuiltinExtensions !== 'undefined') { - enableBuiltinExtensions = environmentService.options._enableBuiltinExtensions; - } else { - enableBuiltinExtensions = true; - } - if (enableBuiltinExtensions) { - return FileAccess.asBrowserUri('../../../../../../extensions', require); - } - return undefined; - } - async scanBuiltinExtensions(): Promise { if (isWeb) { return this.builtinExtensions; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index f981e44f2d846..a9f2c91741181 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -452,13 +452,6 @@ interface IWorkbenchConstructionOptions { */ readonly enabledExtensions?: readonly ExtensionId[]; - /** - * [TEMPORARY]: This will be removed soon. - * Enable inlined extensions. - * Defaults to true. - */ - readonly _enableBuiltinExtensions?: boolean; - /** * Additional domains allowed to open from the workbench without the * link protection popup. From 2fc1dac22902923e6b97f43694a8aa4e0db3fcce Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 6 Dec 2021 17:56:55 +0100 Subject: [PATCH 0313/2210] `ICodeEditor.getContribution` can return `null` --- src/vs/editor/browser/editorBrowser.ts | 2 +- src/vs/editor/browser/editorExtensions.ts | 23 +++++++++++++-- .../editor/browser/widget/codeEditorWidget.ts | 2 +- .../contrib/anchorSelect/anchorSelect.ts | 28 +++++++++---------- .../bracketMatching/bracketMatching.ts | 25 +++++++---------- .../contrib/codeAction/codeActionCommands.ts | 4 +-- .../editor/contrib/codeAction/codeActionUi.ts | 4 +-- .../contrib/codelens/codelensController.ts | 3 ++ .../contrib/colorPicker/colorContributions.ts | 3 ++ .../contrib/colorPicker/colorDetector.ts | 2 +- .../editor/contrib/contextmenu/contextmenu.ts | 15 ++++++---- .../editor/contrib/cursorUndo/cursorUndo.ts | 20 +++++++------ src/vs/editor/contrib/dnd/dnd.ts | 2 +- src/vs/editor/contrib/find/findController.ts | 10 +++---- src/vs/editor/contrib/folding/folding.ts | 6 ++-- src/vs/editor/contrib/gotoError/gotoError.ts | 8 +++--- .../editor/contrib/gotoSymbol/goToCommands.ts | 4 +-- .../link/goToDefinitionAtPosition.ts | 2 +- .../gotoSymbol/peek/referencesController.ts | 6 ++-- .../contrib/hover/colorHoverParticipant.ts | 3 ++ src/vs/editor/contrib/hover/hover.ts | 7 +++-- .../contrib/hover/markerHoverParticipant.ts | 4 +-- .../contrib/inPlaceReplace/inPlaceReplace.ts | 2 +- .../inlineCompletions/ghostTextController.ts | 2 +- .../contrib/linkedEditing/linkedEditing.ts | 2 +- src/vs/editor/contrib/links/links.ts | 4 +-- .../contrib/message/messageController.ts | 2 +- .../editor/contrib/multicursor/multicursor.ts | 11 +++++--- .../contrib/parameterHints/parameterHints.ts | 2 +- src/vs/editor/contrib/rename/rename.ts | 6 ++-- .../editor/contrib/smartSelect/smartSelect.ts | 2 +- .../contrib/snippet/snippetController2.ts | 2 +- src/vs/editor/contrib/suggest/suggest.ts | 2 +- .../contrib/suggest/suggestController.ts | 10 +++++-- src/vs/editor/contrib/suggest/suggestModel.ts | 2 +- .../unicodeHighlighter/unicodeHighlighter.ts | 4 ++- .../viewportSemanticTokens.ts | 2 +- .../wordHighlighter/wordHighlighter.ts | 2 +- .../accessibilityHelp/accessibilityHelp.ts | 2 +- .../browser/inspectTokens/inspectTokens.ts | 2 +- .../standaloneQuickInputServiceImpl.ts | 21 ++++++++------ src/vs/monaco.d.ts | 2 +- .../workbench/api/browser/mainThreadEditor.ts | 2 +- src/vs/workbench/browser/codeeditor.ts | 2 +- .../browser/callHierarchy.contribution.ts | 14 +++++----- .../browser/accessibility/accessibility.ts | 2 +- .../inspectEditorTokens.ts | 4 +-- .../codeEditor/browser/saveParticipants.ts | 2 +- .../browser/commentsEditorContribution.ts | 2 +- .../contrib/comments/browser/commentsView.ts | 4 +-- .../contrib/debug/browser/breakpointsView.ts | 2 +- .../contrib/debug/browser/debugCommands.ts | 2 +- .../debug/browser/debugEditorActions.ts | 8 +++--- .../debug/browser/debugEditorContribution.ts | 2 +- .../contrib/debug/browser/exceptionWidget.ts | 2 +- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../browser/keybindingsEditorContribution.ts | 4 +-- .../contrib/scm/browser/dirtydiffDecorator.ts | 6 ++-- .../contrib/search/browser/searchView.ts | 8 +++--- .../searchEditor/browser/searchEditor.ts | 2 +- .../contrib/snippets/browser/insertSnippet.ts | 2 +- .../contrib/snippets/browser/tabCompletion.ts | 4 +-- .../testing/browser/testingDecorations.ts | 21 +++++++------- .../testing/browser/testingOutputPeek.ts | 24 ++++++++-------- .../browser/typeHierarchy.contribution.ts | 14 +++++----- .../userDataSync/browser/userDataSync.ts | 2 +- .../browser/userDataSyncMergesView.ts | 2 +- 67 files changed, 229 insertions(+), 177 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 6243c0b9dd6b6..1ea6a36412862 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -595,7 +595,7 @@ export interface ICodeEditor extends editorCommon.IEditor { * @id Unique identifier of the contribution. * @return The contribution or null if contribution not found. */ - getContribution(id: string): T; + getContribution(id: string): T | null; /** * Execute `fn` with the editor's services. diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 05e31b0ed7996..435236bde0a3d 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -229,7 +229,7 @@ export abstract class EditorCommand extends Command { /** * Create a command class that is bound to a certain editor contribution. */ - public static bindToContribution(controllerGetter: (editor: ICodeEditor) => T): EditorControllerCommand { + public static bindToContribution(controllerGetter: (editor: ICodeEditor) => T | null): EditorControllerCommand { return class EditorControllerCommandImpl extends EditorCommand { private readonly _callback: (controller: T, args: any) => void; @@ -242,7 +242,7 @@ export abstract class EditorCommand extends Command { public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { const controller = controllerGetter(editor); if (controller) { - this._callback(controllerGetter(editor), args); + this._callback(controller, args); } } }; @@ -351,6 +351,25 @@ export abstract class EditorAction extends EditorCommand { public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise; } +interface IEditorContributionCtorWithGet { + get(editor: ICodeEditor): T | null; +} + +export abstract class EditorControllerAction extends EditorAction { + + protected abstract controller: IEditorContributionCtorWithGet; + + run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { + const controller = this.controller.get(editor); + if (!controller) { + return; + } + return this.runControllerAction(accessor, editor, controller, args); + } + + abstract runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: T, args: any): void | Promise; +} + export type EditorActionImplementation = (accessor: ServicesAccessor, editor: ICodeEditor, args: any) => boolean | Promise; export class MultiEditorAction extends EditorAction { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 75756825872f2..e91453cfb6578 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -976,7 +976,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._focusTracker.refreshState(); } - public getContribution(id: string): T { + public getContribution(id: string): T | null { return (this._contributions[id] || null); } diff --git a/src/vs/editor/contrib/anchorSelect/anchorSelect.ts b/src/vs/editor/contrib/anchorSelect/anchorSelect.ts index 6e8ee1dd0463c..5d9f449cb520d 100644 --- a/src/vs/editor/contrib/anchorSelect/anchorSelect.ts +++ b/src/vs/editor/contrib/anchorSelect/anchorSelect.ts @@ -9,7 +9,7 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./anchorSelect'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorControllerAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -24,7 +24,7 @@ class SelectionAnchorController implements IEditorContribution { public static readonly ID = 'editor.contrib.selectionAnchorController'; - static get(editor: ICodeEditor): SelectionAnchorController { + static get(editor: ICodeEditor): SelectionAnchorController | null { return editor.getContribution(SelectionAnchorController.ID); } @@ -93,7 +93,11 @@ class SelectionAnchorController implements IEditorContribution { } } -class SetSelectionAnchor extends EditorAction { +abstract class SelectionAnchorAction extends EditorControllerAction { + controller = SelectionAnchorController; +} + +class SetSelectionAnchor extends SelectionAnchorAction { constructor() { super({ id: 'editor.action.setSelectionAnchor', @@ -108,13 +112,12 @@ class SetSelectionAnchor extends EditorAction { }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const controller = SelectionAnchorController.get(editor); + async runControllerAction(_accessor: ServicesAccessor, editor: ICodeEditor, controller: SelectionAnchorController): Promise { controller.setSelectionAnchor(); } } -class GoToSelectionAnchor extends EditorAction { +class GoToSelectionAnchor extends SelectionAnchorAction { constructor() { super({ id: 'editor.action.goToSelectionAnchor', @@ -124,13 +127,12 @@ class GoToSelectionAnchor extends EditorAction { }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const controller = SelectionAnchorController.get(editor); + async runControllerAction(_accessor: ServicesAccessor, editor: ICodeEditor, controller: SelectionAnchorController): Promise { controller.goToSelectionAnchor(); } } -class SelectFromAnchorToCursor extends EditorAction { +class SelectFromAnchorToCursor extends SelectionAnchorAction { constructor() { super({ id: 'editor.action.selectFromAnchorToCursor', @@ -145,13 +147,12 @@ class SelectFromAnchorToCursor extends EditorAction { }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const controller = SelectionAnchorController.get(editor); + async runControllerAction(_accessor: ServicesAccessor, editor: ICodeEditor, controller: SelectionAnchorController): Promise { controller.selectFromAnchorToCursor(); } } -class CancelSelectionAnchor extends EditorAction { +class CancelSelectionAnchor extends SelectionAnchorAction { constructor() { super({ id: 'editor.action.cancelSelectionAnchor', @@ -166,8 +167,7 @@ class CancelSelectionAnchor extends EditorAction { }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const controller = SelectionAnchorController.get(editor); + async runControllerAction(_accessor: ServicesAccessor, editor: ICodeEditor, controller: SelectionAnchorController): Promise { controller.cancelSelectionAnchor(); } } diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index 7dbc428c99e2d..c23d5790657b4 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -8,7 +8,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import 'vs/css!./bracketMatching'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorControllerAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -26,7 +26,11 @@ import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/ const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', { dark: '#A0A0A0', light: '#A0A0A0', hc: '#A0A0A0' }, nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.')); -class JumpToBracketAction extends EditorAction { +abstract class BracketMatchingAction extends EditorControllerAction { + controller = BracketMatchingController; +} + +class JumpToBracketAction extends BracketMatchingAction { constructor() { super({ id: 'editor.action.jumpToBracket', @@ -41,16 +45,12 @@ class JumpToBracketAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - let controller = BracketMatchingController.get(editor); - if (!controller) { - return; - } + public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: BracketMatchingController): void { controller.jumpToBracket(); } } -class SelectToBracketAction extends EditorAction { +class SelectToBracketAction extends BracketMatchingAction { constructor() { super({ id: 'editor.action.selectToBracket', @@ -75,12 +75,7 @@ class SelectToBracketAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - const controller = BracketMatchingController.get(editor); - if (!controller) { - return; - } - + public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: BracketMatchingController, args: any): void { let selectBrackets = true; if (args && args.selectBrackets === false) { selectBrackets = false; @@ -106,7 +101,7 @@ class BracketsData { export class BracketMatchingController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.bracketMatchingController'; - public static get(editor: ICodeEditor): BracketMatchingController { + public static get(editor: ICodeEditor): BracketMatchingController | null { return editor.getContribution(BracketMatchingController.ID); } diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index b9f1c9d252d47..165c8de3a894e 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -69,7 +69,7 @@ export class QuickFixController extends Disposable implements IEditorContributio public static readonly ID = 'editor.contrib.quickFixController'; - public static get(editor: ICodeEditor): QuickFixController { + public static get(editor: ICodeEditor): QuickFixController | null { return editor.getContribution(QuickFixController.ID); } @@ -122,7 +122,7 @@ export class QuickFixController extends Disposable implements IEditorContributio return; } - MessageController.get(this._editor).closeMessage(); + MessageController.get(this._editor)?.closeMessage(); const triggerPosition = this._editor.getPosition(); this._trigger({ type: CodeActionTriggerType.Invoke, filter, autoApply, context: { notAvailableMessage, position: triggerPosition } }); } diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index f8ff35a52b0ae..166e6559d4894 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -96,7 +96,7 @@ export class CodeActionUi extends Disposable { if (newState.trigger.context) { const invalidAction = this.getInvalidActionThatWouldHaveBeenApplied(newState.trigger, actions); if (invalidAction && invalidAction.action.disabled) { - MessageController.get(this._editor).showMessage(invalidAction.action.disabled, newState.trigger.context.position); + MessageController.get(this._editor)?.showMessage(invalidAction.action.disabled, newState.trigger.context.position); actions.dispose(); return; } @@ -106,7 +106,7 @@ export class CodeActionUi extends Disposable { const includeDisabledActions = !!newState.trigger.filter?.include; if (newState.trigger.context) { if (!actions.allActions.length || !includeDisabledActions && !actions.validActions.length) { - MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); + MessageController.get(this._editor)?.showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); this._activeCodeActions.value = actions; actions.dispose(); return; diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 648fe1435730d..5c0b75ce168dd 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -469,6 +469,9 @@ registerEditorAction(class ShowLensesInCurrentLine extends EditorAction { const lineNumber = editor.getSelection().positionLineNumber; const codelensController = editor.getContribution(CodeLensContribution.ID); + if (!codelensController) { + return; + } const items: { label: string, command: Command }[] = []; for (let lens of codelensController.getLenses()) { diff --git a/src/vs/editor/contrib/colorPicker/colorContributions.ts b/src/vs/editor/contrib/colorPicker/colorContributions.ts index 1ff15f198412e..0423698694f64 100644 --- a/src/vs/editor/contrib/colorPicker/colorContributions.ts +++ b/src/vs/editor/contrib/colorPicker/colorContributions.ts @@ -47,6 +47,9 @@ export class ColorContribution extends Disposable implements IEditorContribution } const hoverController = this._editor.getContribution(ModesHoverController.ID); + if (!hoverController) { + return; + } if (!hoverController.isColorPickerVisible()) { const range = new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1); hoverController.showContentHover(range, HoverStartMode.Delayed, false); diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index fbbc2fe0103c1..32da1a4b83274 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -89,7 +89,7 @@ export class ColorDetector extends Disposable implements IEditorContribution { return this._editor.getOption(EditorOption.colorDecorators); } - static get(editor: ICodeEditor): ColorDetector { + static get(editor: ICodeEditor): ColorDetector | null { return editor.getContribution(this.ID); } diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index 23a924fa17f7e..0d8805ef5e23d 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -14,7 +14,7 @@ import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isIOS } from 'vs/base/common/platform'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorControllerAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -30,7 +30,7 @@ export class ContextMenuController implements IEditorContribution { public static readonly ID = 'editor.contrib.contextmenu'; - public static get(editor: ICodeEditor): ContextMenuController { + public static get(editor: ICodeEditor): ContextMenuController | null { return editor.getContribution(ContextMenuController.ID); } @@ -264,7 +264,11 @@ export class ContextMenuController implements IEditorContribution { } } -class ShowContextMenu extends EditorAction { +abstract class ContextMenuControllerAction extends EditorControllerAction { + controller = ContextMenuController; +} + +class ShowContextMenu extends ContextMenuControllerAction { constructor() { super({ @@ -280,9 +284,8 @@ class ShowContextMenu extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - let contribution = ContextMenuController.get(editor); - contribution.showContextMenu(); + public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: ContextMenuController): void { + controller.showContextMenu(); } } diff --git a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts index dcb1a4cc1bc08..feaa363904a24 100644 --- a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts +++ b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts @@ -6,7 +6,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorControllerAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -47,7 +47,7 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr public static readonly ID = 'editor.contrib.cursorUndoRedoController'; - public static get(editor: ICodeEditor): CursorUndoRedoController { + public static get(editor: ICodeEditor): CursorUndoRedoController | null { return editor.getContribution(CursorUndoRedoController.ID); } @@ -125,7 +125,11 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr } } -export class CursorUndo extends EditorAction { +abstract class CursorUndoRedoControllerAction extends EditorControllerAction { + controller = CursorUndoRedoController; +} + +export class CursorUndo extends CursorUndoRedoControllerAction { constructor() { super({ id: 'cursorUndo', @@ -140,12 +144,12 @@ export class CursorUndo extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - CursorUndoRedoController.get(editor).cursorUndo(); + public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: CursorUndoRedoController, args: any): void { + controller.cursorUndo(); } } -export class CursorRedo extends EditorAction { +export class CursorRedo extends CursorUndoRedoControllerAction { constructor() { super({ id: 'cursorRedo', @@ -155,8 +159,8 @@ export class CursorRedo extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - CursorUndoRedoController.get(editor).cursorRedo(); + public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: CursorUndoRedoController, args: any): void { + controller.cursorRedo(); } } diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index 3ae8b808eaf17..9ec09a245cb30 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -41,7 +41,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu private _modifierPressed: boolean; static readonly TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl; - static get(editor: ICodeEditor): DragAndDropController { + static get(editor: ICodeEditor): DragAndDropController | null { return editor.getContribution(DragAndDropController.ID); } diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 2fdb1df838dee..99ef6085d5087 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -98,7 +98,7 @@ export class CommonFindController extends Disposable implements IEditorContribut return this._editor; } - public static get(editor: ICodeEditor): CommonFindController { + public static get(editor: ICodeEditor): CommonFindController | null { return editor.getContribution(CommonFindController.ID); } @@ -583,7 +583,7 @@ export class StartFindWithArgsAction extends EditorAction { } public async run(accessor: ServicesAccessor | null, editor: ICodeEditor, args?: IFindStartArguments): Promise { - let controller = CommonFindController.get(editor); + const controller = CommonFindController.get(editor); if (controller) { const newState: INewFindReplaceState = args ? { searchString: args.searchString, @@ -635,7 +635,7 @@ export class StartFindWithSelectionAction extends EditorAction { } public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise { - let controller = CommonFindController.get(editor); + const controller = CommonFindController.get(editor); if (controller) { await controller.start({ forceRevealReplace: false, @@ -654,7 +654,7 @@ export class StartFindWithSelectionAction extends EditorAction { } export abstract class MatchFindAction extends EditorAction { public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise { - let controller = CommonFindController.get(editor); + const controller = CommonFindController.get(editor); if (controller && !this._run(controller)) { await controller.start({ forceRevealReplace: false, @@ -734,7 +734,7 @@ export class PreviousMatchFindAction extends MatchFindAction { export abstract class SelectionMatchFindAction extends EditorAction { public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise { - let controller = CommonFindController.get(editor); + const controller = CommonFindController.get(editor); if (!controller) { return; } diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index b9d811d55f5b3..67b387117e676 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -58,7 +58,7 @@ export class FoldingController extends Disposable implements IEditorContribution static readonly MAX_FOLDING_REGIONS = 5000; - public static get(editor: ICodeEditor): FoldingController { + public static get(editor: ICodeEditor): FoldingController | null { return editor.getContribution(FoldingController.ID); } @@ -511,11 +511,11 @@ abstract class FoldingAction extends EditorAction { abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T): void; public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise { - let foldingController = FoldingController.get(editor); + const foldingController = FoldingController.get(editor); if (!foldingController) { return; } - let foldingModelPromise = foldingController.getFoldingModel(); + const foldingModelPromise = foldingController.getFoldingModel(); if (foldingModelPromise) { this.reportTelemetry(accessor, editor); return foldingModelPromise.then(foldingModel => { diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index f8079d8cf4f3e..10f4b12814cd4 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -29,7 +29,7 @@ export class MarkerController implements IEditorContribution { static readonly ID = 'editor.contrib.markerController'; - static get(editor: ICodeEditor): MarkerController { + static get(editor: ICodeEditor): MarkerController | null { return editor.getContribution(MarkerController.ID); } @@ -154,8 +154,8 @@ export class MarkerController implements IEditorContribution { }, this._editor); if (otherEditor) { - MarkerController.get(otherEditor).close(); - MarkerController.get(otherEditor).nagivate(next, multiFile); + MarkerController.get(otherEditor)?.close(); + MarkerController.get(otherEditor)?.nagivate(next, multiFile); } } else { @@ -178,7 +178,7 @@ class MarkerNavigationAction extends EditorAction { async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { if (editor.hasModel()) { - MarkerController.get(editor).nagivate(this._next, this._multiFile); + MarkerController.get(editor)?.nagivate(this._next, this._multiFile); } } } diff --git a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts index b1a4b7ef2cd41..0f41d7e2c74f2 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -108,7 +108,7 @@ abstract class SymbolNavigationAction extends EditorAction { // no result -> show message if (!this._configuration.muteMessage) { const info = model.getWordAtPosition(pos); - MessageController.get(editor).showMessage(this._getNoResultFoundMessage(info), pos); + MessageController.get(editor)?.showMessage(this._getNoResultFoundMessage(info), pos); } } else if (referenceCount === 1 && altAction) { // already at the only result, run alternative @@ -202,7 +202,7 @@ abstract class SymbolNavigationAction extends EditorAction { } private _openInPeek(target: ICodeEditor, model: ReferencesModel) { - let controller = ReferencesController.get(target); + const controller = ReferencesController.get(target); if (controller && target.hasModel()) { controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), this._configuration.openInPeek); } else { diff --git a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index 655395a7abadd..c078803a8cfe0 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -76,7 +76,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri })); } - static get(editor: ICodeEditor): GotoDefinitionAtPositionEditorContribution { + static get(editor: ICodeEditor): GotoDefinitionAtPositionEditorContribution | null { return editor.getContribution(GotoDefinitionAtPositionEditorContribution.ID); } diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index af4158ba55e8f..22115352c83dd 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -43,7 +43,7 @@ export abstract class ReferencesController implements IEditorContribution { private readonly _referenceSearchVisible: IContextKey; - static get(editor: ICodeEditor): ReferencesController { + static get(editor: ICodeEditor): ReferencesController | null { return editor.getContribution(ReferencesController.ID); } @@ -270,7 +270,7 @@ export abstract class ReferencesController implements IEditorContribution { this.closeWidget(); openedEditor.focus(); - other.toggleWidget( + other?.toggleWidget( range, createCancelablePromise(_ => Promise.resolve(model)), this._peekMode ?? false @@ -302,7 +302,7 @@ function withController(accessor: ServicesAccessor, fn: (controller: ReferencesC if (!outerEditor) { return; } - let controller = ReferencesController.get(outerEditor); + const controller = ReferencesController.get(outerEditor); if (controller) { fn(controller); } diff --git a/src/vs/editor/contrib/hover/colorHoverParticipant.ts b/src/vs/editor/contrib/hover/colorHoverParticipant.ts index 5160e65122f6f..9fc2d86c0c283 100644 --- a/src/vs/editor/contrib/hover/colorHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/colorHoverParticipant.ts @@ -64,6 +64,9 @@ export class ColorHoverParticipant implements IEditorHoverParticipant; - static get(editor: ICodeEditor): ModesHoverController { + static get(editor: ICodeEditor): ModesHoverController | null { return editor.getContribution(ModesHoverController.ID); } @@ -283,7 +283,7 @@ class ShowDefinitionPreviewHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - let controller = ModesHoverController.get(editor); + const controller = ModesHoverController.get(editor); if (!controller) { return; } @@ -295,6 +295,9 @@ class ShowDefinitionPreviewHoverAction extends EditorAction { const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); const goto = GotoDefinitionAtPositionEditorContribution.get(editor); + if (!goto) { + return; + } const promise = goto.startFindDefinitionFromCursor(position); promise.then(() => { controller.showContentHover(range, HoverStartMode.Immediate, true); diff --git a/src/vs/editor/contrib/hover/markerHoverParticipant.ts b/src/vs/editor/contrib/hover/markerHoverParticipant.ts index d61e79b638f96..fa17f20b7ff55 100644 --- a/src/vs/editor/contrib/hover/markerHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markerHoverParticipant.ts @@ -173,7 +173,7 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant { this._hover.hide(); - MarkerController.get(this._editor).showAtMarker(markerHover.marker); + MarkerController.get(this._editor)?.showAtMarker(markerHover.marker); this._editor.focus(); } }); @@ -225,7 +225,7 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant(InPlaceReplaceController.ID); } diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts index 2213989a1fc28..2c17fadf7e736 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts @@ -27,7 +27,7 @@ export class GhostTextController extends Disposable { static ID = 'editor.contrib.ghostTextController'; - public static get(editor: ICodeEditor): GhostTextController { + public static get(editor: ICodeEditor): GhostTextController | null { return editor.getContribution(GhostTextController.ID); } diff --git a/src/vs/editor/contrib/linkedEditing/linkedEditing.ts b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts index a029a2cb44d16..37c47c71cf620 100644 --- a/src/vs/editor/contrib/linkedEditing/linkedEditing.ts +++ b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts @@ -44,7 +44,7 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont className: DECORATION_CLASS_NAME }); - static get(editor: ICodeEditor): LinkedEditingContribution { + static get(editor: ICodeEditor): LinkedEditingContribution | null { return editor.getContribution(LinkedEditingContribution.ID); } diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 809085bb818c1..6c5170e8456f5 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -116,7 +116,7 @@ export class LinkDetector implements IEditorContribution { public static readonly ID: string = 'editor.linkDetector'; - public static get(editor: ICodeEditor): LinkDetector { + public static get(editor: ICodeEditor): LinkDetector | null { return editor.getContribution(LinkDetector.ID); } @@ -404,7 +404,7 @@ class OpenLinkAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - let linkDetector = LinkDetector.get(editor); + const linkDetector = LinkDetector.get(editor); if (!linkDetector) { return; } diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts index 5efbb4e9be042..5bbc1908fb847 100644 --- a/src/vs/editor/contrib/message/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -23,7 +23,7 @@ export class MessageController implements IEditorContribution { static readonly MESSAGE_VISIBLE = new RawContextKey('messageVisible', false, nls.localize('messageVisible', 'Whether the editor is currently showing an inline message')); - static get(editor: ICodeEditor): MessageController { + static get(editor: ICodeEditor): MessageController | null { return editor.getContribution(MessageController.ID); } diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 9eebf15826b8b..805900d7da09f 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -458,7 +458,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito private _session: MultiCursorSession | null; private readonly _sessionDispose = this._register(new DisposableStore()); - public static get(editor: ICodeEditor): MultiCursorSelectionController { + public static get(editor: ICodeEditor): MultiCursorSelectionController | null { return editor.getContribution(MultiCursorSelectionController.ID); } @@ -897,9 +897,12 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut this.updateSoon.schedule(); } })); - this._register(CommonFindController.get(editor).getState().onFindReplaceStateChange((e) => { - this._update(); - })); + const findController = CommonFindController.get(editor); + if (findController) { + this._register(findController.getState().onFindReplaceStateChange((e) => { + this._update(); + })); + } } private _update(): void { diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.ts b/src/vs/editor/contrib/parameterHints/parameterHints.ts index 13c86afa0bf45..3ca7c6023cb97 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHints.ts @@ -22,7 +22,7 @@ class ParameterHintsController extends Disposable implements IEditorContribution public static readonly ID = 'editor.controller.parameterHints'; - public static get(editor: ICodeEditor): ParameterHintsController { + public static get(editor: ICodeEditor): ParameterHintsController | null { return editor.getContribution(ParameterHintsController.ID); } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 9a71f4ef8684c..1c7bfc2fb6d20 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -124,7 +124,7 @@ class RenameController implements IEditorContribution { public static readonly ID = 'editor.contrib.renameController'; - static get(editor: ICodeEditor): RenameController { + static get(editor: ICodeEditor): RenameController | null { return editor.getContribution(RenameController.ID); } @@ -173,7 +173,7 @@ class RenameController implements IEditorContribution { this._progressService.showWhile(resolveLocationOperation, 250); loc = await resolveLocationOperation; } catch (e) { - MessageController.get(this.editor).showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position); + MessageController.get(this.editor)?.showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position); return undefined; } @@ -182,7 +182,7 @@ class RenameController implements IEditorContribution { } if (loc.rejectReason) { - MessageController.get(this.editor).showMessage(loc.rejectReason, position); + MessageController.get(this.editor)?.showMessage(loc.rejectReason, position); return undefined; } diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index 496ed00c9ca74..0e6b5eaed60f6 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -50,7 +50,7 @@ class SmartSelectController implements IEditorContribution { static readonly ID = 'editor.contrib.smartSelectController'; - static get(editor: ICodeEditor): SmartSelectController { + static get(editor: ICodeEditor): SmartSelectController | null { return editor.getContribution(SmartSelectController.ID); } diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index a67846f68d18b..273eb1d80ea54 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -45,7 +45,7 @@ export class SnippetController2 implements IEditorContribution { public static readonly ID = 'snippetController2'; - static get(editor: ICodeEditor): SnippetController2 { + static get(editor: ICodeEditor): SnippetController2 | null { return editor.getContribution(SnippetController2.ID); } diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 36c02a93d6fe2..429582cf8077c 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -409,7 +409,7 @@ modes.CompletionProviderRegistry.register('*', _provider); export function showSimpleSuggestions(editor: ICodeEditor, suggestions: modes.CompletionItem[]) { setTimeout(() => { _provider.onlyOnceSuggestions.push(...suggestions); - editor.getContribution('editor.contrib.suggestController').triggerSuggest(new Set().add(_provider)); + editor.getContribution('editor.contrib.suggestController')?.triggerSuggest(new Set().add(_provider)); }, 0); } diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 42e2561cef100..01c7401600883 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -104,7 +104,7 @@ export class SuggestController implements IEditorContribution { public static readonly ID: string = 'editor.contrib.suggestController'; - public static get(editor: ICodeEditor): SuggestController { + public static get(editor: ICodeEditor): SuggestController | null { return editor.getContribution(SuggestController.ID); } @@ -284,6 +284,10 @@ export class SuggestController implements IEditorContribution { if (!this.editor.hasModel()) { return; } + const snippetController = SnippetController2.get(this.editor); + if (!snippetController) { + return; + } const model = this.editor.getModel(); const modelVersionNow = model.getAlternativeVersionId(); @@ -377,7 +381,7 @@ export class SuggestController implements IEditorContribution { insertText = SnippetParser.escape(insertText); } - SnippetController2.get(this.editor).insert(insertText, { + snippetController.insert(insertText, { overwriteBefore: info.overwriteBefore, overwriteAfter: info.overwriteAfter, undoStopBefore: false, @@ -984,6 +988,6 @@ registerEditorAction(class extends EditorAction { } run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - SuggestController.get(editor).resetWidgetSize(); + SuggestController.get(editor)?.resetWidgetSize(); } }); diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index af2703631f1d3..c519ea09ba2ec 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -378,7 +378,7 @@ export class SuggestModel implements IDisposable { return; } - if (this._editor.getOption(EditorOption.suggest).snippetsPreventQuickSuggestions && SnippetController2.get(this._editor).isInSnippet()) { + if (this._editor.getOption(EditorOption.suggest).snippetsPreventQuickSuggestions && SnippetController2.get(this._editor)?.isInSnippet()) { // no quick suggestion when in snippet mode return; } diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index 1d62875aa71b2..77b3b27492da0 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -410,7 +410,9 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa const model = this._editor.getModel(); const unicodeHighlighter = this._editor.getContribution(UnicodeHighlighter.ID); - + if (!unicodeHighlighter) { + return []; + } const result: MarkdownHover[] = []; let index = 300; diff --git a/src/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.ts b/src/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.ts index 5dcb8e25cc4ba..6f6da8943bd33 100644 --- a/src/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.ts +++ b/src/vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens.ts @@ -22,7 +22,7 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo public static readonly ID = 'editor.contrib.viewportSemanticTokens'; - public static get(editor: ICodeEditor): ViewportSemanticTokensContribution { + public static get(editor: ICodeEditor): ViewportSemanticTokensContribution | null { return editor.getContribution(ViewportSemanticTokensContribution.ID); } diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index 3b90014315b22..747c815d70dcd 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -494,7 +494,7 @@ class WordHighlighterContribution extends Disposable implements IEditorContribut public static readonly ID = 'editor.contrib.wordHighlighter'; - public static get(editor: ICodeEditor): WordHighlighterContribution { + public static get(editor: ICodeEditor): WordHighlighterContribution | null { return editor.getContribution(WordHighlighterContribution.ID); } diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index e9e8e4b9f5947..a6fad0226d7e4 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -38,7 +38,7 @@ class AccessibilityHelpController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.accessibilityHelpController'; - public static get(editor: ICodeEditor): AccessibilityHelpController { + public static get(editor: ICodeEditor): AccessibilityHelpController | null { return editor.getContribution( AccessibilityHelpController.ID ); diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 416f9d9b2d308..e588b8c45d536 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -29,7 +29,7 @@ class InspectTokensController extends Disposable implements IEditorContribution public static readonly ID = 'editor.contrib.inspectTokens'; - public static get(editor: ICodeEditor): InspectTokensController { + public static get(editor: ICodeEditor): InspectTokensController | null { return editor.getContribution(InspectTokensController.ID); } diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts index 4c4bd06b51ae9..d215bfb802e0b 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts @@ -36,13 +36,18 @@ export class EditorScopedQuickInputServiceImpl extends QuickInputService { // Use the passed in code editor as host for the quick input widget const contribution = QuickInputEditorContribution.get(editor); - this.host = { - _serviceBrand: undefined, - get container() { return contribution.widget.getDomNode(); }, - get dimension() { return editor.getLayoutInfo(); }, - get onDidLayout() { return editor.onDidLayoutChange; }, - focus: () => editor.focus() - }; + if (contribution) { + const widget = contribution.widget; + this.host = { + _serviceBrand: undefined, + get container() { return widget.getDomNode(); }, + get dimension() { return editor.getLayoutInfo(); }, + get onDidLayout() { return editor.onDidLayoutChange; }, + focus: () => editor.focus() + }; + } else { + this.host = undefined; + } } protected override createController(): QuickInputController { @@ -135,7 +140,7 @@ export class QuickInputEditorContribution implements IEditorContribution { static readonly ID = 'editor.controller.quickInput'; - static get(editor: ICodeEditor): QuickInputEditorContribution { + static get(editor: ICodeEditor): QuickInputEditorContribution | null { return editor.getContribution(QuickInputEditorContribution.ID); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 16dc97937443f..180bbd2b31942 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4918,7 +4918,7 @@ declare namespace monaco.editor { * @id Unique identifier of the contribution. * @return The contribution or null if contribution not found. */ - getContribution(id: string): T; + getContribution(id: string): T | null; /** * Type the getModel() of IEditor. */ diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 626c49bf09ccf..941611886ea9f 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -527,7 +527,7 @@ export class MainThreadTextEditor { this._codeEditor.focus(); // make modifications - snippetController.insert(template, { + snippetController?.insert(template, { overwriteBefore: 0, overwriteAfter: 0, undoStopBefore: opts.undoStopBefore, undoStopAfter: opts.undoStopAfter, clipboardText diff --git a/src/vs/workbench/browser/codeeditor.ts b/src/vs/workbench/browser/codeeditor.ts index 46ae4f0d265de..9738fc2a49246 100644 --- a/src/vs/workbench/browser/codeeditor.ts +++ b/src/vs/workbench/browser/codeeditor.ts @@ -211,7 +211,7 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget { export class OpenWorkspaceButtonContribution extends Disposable implements IEditorContribution { - static get(editor: ICodeEditor): OpenWorkspaceButtonContribution { + static get(editor: ICodeEditor): OpenWorkspaceButtonContribution | null { return editor.getContribution(OpenWorkspaceButtonContribution.ID); } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 7be3cf0a6d5f6..7eed240c403f8 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -40,7 +40,7 @@ class CallHierarchyController implements IEditorContribution { static readonly Id = 'callHierarchy'; - static get(editor: ICodeEditor): CallHierarchyController { + static get(editor: ICodeEditor): CallHierarchyController | null { return editor.getContribution(CallHierarchyController.Id); } @@ -112,7 +112,7 @@ class CallHierarchyController implements IEditorContribution { const newModel = model.fork(call.item); this._sessionDisposables.clear(); - CallHierarchyController.get(newEditor)._showCallHierarchyWidget( + CallHierarchyController.get(newEditor)?._showCallHierarchyWidget( Range.lift(newModel.root.selectionRange).getStartPosition(), this._widget.direction, Promise.resolve(newModel), @@ -198,7 +198,7 @@ registerAction2(class extends EditorAction2 { } async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - return CallHierarchyController.get(editor).startCallHierarchyFromEditor(); + return CallHierarchyController.get(editor)?.startCallHierarchyFromEditor(); } }); @@ -223,7 +223,7 @@ registerAction2(class extends EditorAction2 { } runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { - return CallHierarchyController.get(editor).showIncomingCalls(); + return CallHierarchyController.get(editor)?.showIncomingCalls(); } }); @@ -248,7 +248,7 @@ registerAction2(class extends EditorAction2 { } runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { - return CallHierarchyController.get(editor).showOutgoingCalls(); + return CallHierarchyController.get(editor)?.showOutgoingCalls(); } }); @@ -268,7 +268,7 @@ registerAction2(class extends EditorAction2 { } async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - return CallHierarchyController.get(editor).startCallHierarchyFromCallHierarchy(); + return CallHierarchyController.get(editor)?.startCallHierarchyFromCallHierarchy(); } }); @@ -296,6 +296,6 @@ registerAction2(class extends EditorAction2 { } runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void { - return CallHierarchyController.get(editor).endCallHierarchy(); + return CallHierarchyController.get(editor)?.endCallHierarchy(); } }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index cbb37bd2383bc..344afbe6f33f3 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -41,7 +41,7 @@ export class AccessibilityHelpController extends Disposable implements IEditorCo public static readonly ID = 'editor.contrib.accessibilityHelpController'; - public static get(editor: ICodeEditor): AccessibilityHelpController { + public static get(editor: ICodeEditor): AccessibilityHelpController | null { return editor.getContribution(AccessibilityHelpController.ID); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 015a2a1fe3d22..61d4f2099de6c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -39,7 +39,7 @@ class InspectEditorTokensController extends Disposable implements IEditorContrib public static readonly ID = 'editor.contrib.inspectEditorTokens'; - public static get(editor: ICodeEditor): InspectEditorTokensController { + public static get(editor: ICodeEditor): InspectEditorTokensController | null { return editor.getContribution(InspectEditorTokensController.ID); } @@ -249,7 +249,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { this._notificationService.warn(err); setTimeout(() => { - InspectEditorTokensController.get(this._editor).stop(); + InspectEditorTokensController.get(this._editor)?.stop(); }); }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 80be1245cd90c..0b292e13ec246 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -62,7 +62,7 @@ export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { prevSelection = editor.getSelections(); if (isAutoSaved) { cursors = prevSelection.map(s => s.getPosition()); - const snippetsRange = SnippetController2.get(editor).getSessionEnclosingRange(); + const snippetsRange = SnippetController2.get(editor)?.getSessionEnclosingRange(); if (snippetsRange) { for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) { cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber))); diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 1114088c35c14..a7de3166370e5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -243,7 +243,7 @@ export class CommentController implements IEditorContribution { } } - public static get(editor: ICodeEditor): CommentController { + public static get(editor: ICodeEditor): CommentController | null { return editor.getContribution(ID); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 914d1cfc22b3a..df677b0071340 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -218,7 +218,7 @@ export class CommentsPanel extends ViewPane { const control = this.editorService.activeTextEditorControl; if (threadToReveal && isCodeEditor(control)) { const controller = CommentController.get(control); - controller.revealCommentThread(threadToReveal, commentToReveal, false); + controller?.revealCommentThread(threadToReveal, commentToReveal, false); } return true; @@ -239,7 +239,7 @@ export class CommentsPanel extends ViewPane { const control = editor.getControl(); if (threadToReveal && isCodeEditor(control)) { const controller = CommentController.get(control); - controller.revealCommentThread(threadToReveal, commentToReveal.uniqueIdInThread, true); + controller?.revealCommentThread(threadToReveal, commentToReveal.uniqueIdInThread, true); } } }); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 6c0226e8e4add..d911c850057f5 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -1365,7 +1365,7 @@ registerAction2(class extends ViewAction { if (editor) { const codeEditor = editor.getControl(); if (isCodeEditor(codeEditor)) { - codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(breakpoint.lineNumber, breakpoint.column); + codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID)?.showBreakpointWidget(breakpoint.lineNumber, breakpoint.column); } } } else if (breakpoint instanceof FunctionBreakpoint) { diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 94e5c94835902..32415cab8ec31 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -624,7 +624,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (editor && !created) { const codeEditor = editor.getControl(); if (codeEditor) { - await codeEditor.getContribution(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration(); + await codeEditor.getContribution(EDITOR_CONTRIBUTION_ID)?.addLaunchConfiguration(); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index d4951da6c8b3c..f1f8e7f860650 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -96,7 +96,7 @@ class ConditionalBreakpointAction extends EditorAction2 { const position = editor.getPosition(); if (position && editor.hasModel() && debugService.canSetBreakpointsIn(editor.getModel())) { - editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, undefined, BreakpointWidgetContext.CONDITION); + editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID)?.showBreakpointWidget(position.lineNumber, undefined, BreakpointWidgetContext.CONDITION); } } } @@ -127,7 +127,7 @@ class LogPointAction extends EditorAction2 { const position = editor.getPosition(); if (position && editor.hasModel() && debugService.canSetBreakpointsIn(editor.getModel())) { - editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE); + editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID)?.showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE); } } } @@ -326,7 +326,7 @@ class ShowDebugHoverAction extends EditorAction { } const range = new Range(position.lineNumber, position.column, position.lineNumber, word.endColumn); - return editor.getContribution(EDITOR_CONTRIBUTION_ID).showHover(range, true); + return editor.getContribution(EDITOR_CONTRIBUTION_ID)?.showHover(range, true); } } @@ -458,7 +458,7 @@ class CloseExceptionWidgetAction extends EditorAction { async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { const contribution = editor.getContribution(EDITOR_CONTRIBUTION_ID); - contribution.closeExceptionWidget(); + contribution?.closeExceptionWidget(); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 5efdae15f8ab3..a04af1972d2d8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -295,7 +295,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (debugHoverWasVisible && this.hoverRange) { // If the debug hover was visible immediately show the editor hover for the alt transition to be smooth const hoverController = this.editor.getContribution(ModesHoverController.ID); - hoverController.showContentHover(this.hoverRange, HoverStartMode.Immediate, false); + hoverController?.showContentHover(this.hoverRange, HoverStartMode.Immediate, false); } const onKeyUp = new DomEmitter(document, 'keyup'); diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 430143af84cff..8f51b53ae7917 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -82,7 +82,7 @@ export class ExceptionWidget extends ZoneWidget { const actionBar = new ActionBar(actions); actionBar.push(new Action('editor.closeExceptionWidget', nls.localize('close', "Close"), ThemeIcon.asClassName(widgetClose), true, async () => { const contribution = this.editor.getContribution(EDITOR_CONTRIBUTION_ID); - contribution.closeExceptionWidget(); + contribution?.closeExceptionWidget(); }), { label: false, icon: true }); dom.append(container, title); diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 79bd4ebc43d49..a6c347ead2839 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -756,7 +756,7 @@ class AcceptReplInputAction extends EditorAction { } run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { - SuggestController.get(editor).cancelSuggestWidget(); + SuggestController.get(editor)?.cancelSuggestWidget(); const repl = getReplView(accessor.get(IViewsService)); repl?.acceptReplInput(); } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index 8e8cae61421ce..b47fcfcec0a40 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -41,7 +41,7 @@ export class DefineKeybindingController extends Disposable implements IEditorCon public static readonly ID = 'editor.contrib.defineKeybinding'; - static get(editor: ICodeEditor): DefineKeybindingController { + static get(editor: ICodeEditor): DefineKeybindingController | null { return editor.getContribution(DefineKeybindingController.ID); } @@ -155,7 +155,7 @@ export class KeybindingWidgetRenderer extends Disposable { snippetText = smartInsertInfo.prepend + snippetText + smartInsertInfo.append; this._editor.setPosition(smartInsertInfo.position); - SnippetController2.get(this._editor).insert(snippetText, { overwriteBefore: 0, overwriteAfter: 0 }); + SnippetController2.get(this._editor)?.insert(snippetText, { overwriteBefore: 0, overwriteAfter: 0 }); } } } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 00f34c3fea90a..63d2a63ceff37 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -556,7 +556,7 @@ export class DirtyDiffController extends Disposable implements IEditorContributi public static readonly ID = 'editor.contrib.dirtydiff'; - static get(editor: ICodeEditor): DirtyDiffController { + static get(editor: ICodeEditor): DirtyDiffController | null { return editor.getContribution(DirtyDiffController.ID); } @@ -1410,7 +1410,9 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor .map(editor => { const codeEditor = editor as CodeEditorWidget; const controller = DirtyDiffController.get(codeEditor); - controller.modelRegistry = this; + if (controller) { + controller.modelRegistry = this; + } return codeEditor.getModel(); }) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 6e08ef59abd87..4efb971169ffc 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -23,7 +23,7 @@ import * as strings from 'vs/base/common/strings'; import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchview'; -import { getCodeEditor, ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { getCodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -912,8 +912,8 @@ export class SearchView extends ViewPane { updateTextFromFindWidgetOrSelection({ allowUnselectedWord = true, allowSearchOnType = true }): boolean { let activeEditor = this.editorService.activeTextEditorControl; if (isCodeEditor(activeEditor) && !activeEditor?.hasTextFocus()) { - const controller = CommonFindController.get(activeEditor as ICodeEditor); - if (controller.isFindInputFocused()) { + const controller = CommonFindController.get(activeEditor); + if (controller && controller.isFindInputFocused()) { return this.updateTextFromFindWidget(controller, { allowSearchOnType }); } @@ -1748,7 +1748,7 @@ export class SearchView extends ViewPane { const codeEditor = getCodeEditor(editor.getControl()); if (codeEditor) { const multiCursorController = MultiCursorSelectionController.get(codeEditor); - multiCursorController.selectAllUsingSelections(selections); + multiCursorController?.selectAllUsingSelections(selections); } } } diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 90568d84e5c52..fbef2f7a89e42 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -557,7 +557,7 @@ export class SearchEditor extends BaseTextEditor { } const controller = ReferencesController.get(this.searchResultEditor); - controller.closeWidget(false); + controller?.closeWidget(false); const labelFormatter = (uri: URI): string => this.labelService.getUriLabel(uri, { relative: true }); const results = serializeSearchResultForEditor(this.searchModel.searchResult, startConfig.filesToInclude, startConfig.filesToExclude, startConfig.contextLines, labelFormatter, sortOrder, searchOperation?.limitHit); const { resultsModel } = await input.getModels(); diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index df0f7ee2307e7..7e0479f2f244e 100644 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -144,7 +144,7 @@ class InsertSnippetAction extends EditorAction { if (snippet.needsClipboard) { clipboardText = await clipboardService.readText(); } - SnippetController2.get(editor).insert(snippet.codeSnippet, { clipboardText }); + SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); } private async _pickSnippet(snippetService: ISnippetsService, quickInputService: IQuickInputService, languageId: string): Promise { diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index db12da69bef2b..180ced69a135f 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -27,7 +27,7 @@ export class TabCompletionController implements IEditorContribution { public static readonly ID = 'editor.tabCompletionController'; static readonly ContextKey = new RawContextKey('hasSnippetCompletions', undefined); - public static get(editor: ICodeEditor): TabCompletionController { + public static get(editor: ICodeEditor): TabCompletionController | null { return editor.getContribution(TabCompletionController.ID); } @@ -140,7 +140,7 @@ export class TabCompletionController implements IEditorContribution { return; } } - SnippetController2.get(this._editor).insert(snippet.codeSnippet, { + SnippetController2.get(this._editor)?.insert(snippet.codeSnippet, { overwriteBefore: snippet.prefix.length, overwriteAfter: 0, clipboardText }); diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 2de88d4f960d2..b91ca23b4f027 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -271,7 +271,7 @@ export class TestingDecorations extends Disposable implements IEditorContributio /** * Gets the decorations associated with the given code editor. */ - public static get(editor: ICodeEditor): TestingDecorations { + public static get(editor: ICodeEditor): TestingDecorations | null { return editor.getContribution(Testing.DecorationsContributionId); } @@ -661,15 +661,16 @@ abstract class RunTestDecoration { let actions = this.getContextMenuActions(e); const editor = this.codeEditorService.listCodeEditors().find(e => e.getModel() === this.model); if (editor) { - actions = { - dispose: actions.dispose, - object: Separator.join( - actions.object, - editor - .getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID) - .getContextMenuActionsAtPosition(this.line, this.model) - ) - }; + const contribution = editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID); + if (contribution) { + actions = { + dispose: actions.dispose, + object: Separator.join( + actions.object, + contribution.getContextMenuActionsAtPosition(this.line, this.model) + ) + }; + } } this.contextMenuService.showContextMenu({ diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index cd6af6cdda44f..6f7edfb5a76e1 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -224,7 +224,7 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener } this.lastUri = uri; - TestingOutputPeekController.get(control).show(buildTestUri(this.lastUri)); + TestingOutputPeekController.get(control)?.show(buildTestUri(this.lastUri)); return true; } @@ -377,7 +377,7 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo /** * Gets the controller associated with the given code editor. */ - public static get(editor: ICodeEditor): TestingOutputPeekController { + public static get(editor: ICodeEditor): TestingOutputPeekController | null { return editor.getContribution(Testing.OutputPeekContributionId); } @@ -501,8 +501,8 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo }, this.editor); if (otherEditor) { - TestingOutputPeekController.get(otherEditor).removePeek(); - return TestingOutputPeekController.get(otherEditor).show(uri); + TestingOutputPeekController.get(otherEditor)?.removePeek(); + return TestingOutputPeekController.get(otherEditor)?.show(uri); } } @@ -1050,7 +1050,7 @@ export class CloseTestPeek extends EditorAction2 { runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { const parent = getOuterEditorFromDiffEditor(accessor); - TestingOutputPeekController.get(parent ?? editor).removePeek(); + TestingOutputPeekController.get(parent ?? editor)?.removePeek(); } } @@ -1316,7 +1316,7 @@ class OutputPeekTree extends Disposable { if (!dto.revealLocation) { peekController.showInPlace(dto); } else { - TestingOutputPeekController.get(editor).openAndShow(dto.messageUri); + TestingOutputPeekController.get(editor)?.openAndShow(dto.messageUri); } })); @@ -1584,7 +1584,7 @@ const navWhen = ContextKeyExpr.and( * editor is embedded (i.e. inside a peek already). */ const getPeekedEditor = (accessor: ServicesAccessor, editor: ICodeEditor) => { - if (TestingOutputPeekController.get(editor).isVisible) { + if (TestingOutputPeekController.get(editor)?.isVisible) { return editor; } @@ -1626,7 +1626,7 @@ export class GoToNextMessageAction extends EditorAction2 { } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) { - TestingOutputPeekController.get(getPeekedEditor(accessor, editor)).next(); + TestingOutputPeekController.get(getPeekedEditor(accessor, editor))?.next(); } } @@ -1656,7 +1656,7 @@ export class GoToPreviousMessageAction extends EditorAction2 { } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) { - TestingOutputPeekController.get(getPeekedEditor(accessor, editor)).previous(); + TestingOutputPeekController.get(getPeekedEditor(accessor, editor))?.previous(); } } @@ -1674,7 +1674,7 @@ export class OpenMessageInEditorAction extends EditorAction2 { } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) { - TestingOutputPeekController.get(getPeekedEditor(accessor, editor)).openCurrentInEditor(); + TestingOutputPeekController.get(getPeekedEditor(accessor, editor))?.openCurrentInEditor(); } } @@ -1702,6 +1702,8 @@ export class ToggleTestingPeekHistory extends EditorAction2 { public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) { const ctrl = TestingOutputPeekController.get(getPeekedEditor(accessor, editor)); - ctrl.historyVisible.value = !ctrl.historyVisible.value; + if (ctrl) { + ctrl.historyVisible.value = !ctrl.historyVisible.value; + } } } diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts index 1737c94a2b488..9b16dbfab2cf5 100644 --- a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts @@ -38,7 +38,7 @@ function sanitizedDirection(candidate: string): TypeHierarchyDirection { class TypeHierarchyController implements IEditorContribution { static readonly Id = 'typeHierarchy'; - static get(editor: ICodeEditor): TypeHierarchyController { + static get(editor: ICodeEditor): TypeHierarchyController | null { return editor.getContribution(TypeHierarchyController.Id); } @@ -140,7 +140,7 @@ class TypeHierarchyController implements IEditorContribution { const newModel = model.fork(typeItem.item); this._sessionDisposables.clear(); - TypeHierarchyController.get(newEditor)._showTypeHierarchyWidget( + TypeHierarchyController.get(newEditor)?._showTypeHierarchyWidget( Range.lift(newModel.root.selectionRange).getStartPosition(), this._widget.direction, Promise.resolve(newModel), @@ -191,7 +191,7 @@ registerAction2(class extends EditorAction2 { } async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - return TypeHierarchyController.get(editor).startTypeHierarchyFromEditor(); + return TypeHierarchyController.get(editor)?.startTypeHierarchyFromEditor(); } }); @@ -217,7 +217,7 @@ registerAction2(class extends EditorAction2 { } runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { - return TypeHierarchyController.get(editor).showSupertypes(); + return TypeHierarchyController.get(editor)?.showSupertypes(); } }); @@ -242,7 +242,7 @@ registerAction2(class extends EditorAction2 { } runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { - return TypeHierarchyController.get(editor).showSubtypes(); + return TypeHierarchyController.get(editor)?.showSubtypes(); } }); @@ -261,7 +261,7 @@ registerAction2(class extends EditorAction2 { } async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - return TypeHierarchyController.get(editor).startTypeHierarchyFromTypeHierarchy(); + return TypeHierarchyController.get(editor)?.startTypeHierarchyFromTypeHierarchy(); } }); @@ -288,6 +288,6 @@ registerAction2(class extends EditorAction2 { } runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void { - return TypeHierarchyController.get(editor).endTypeHierarchy(); + return TypeHierarchyController.get(editor)?.endTypeHierarchy(); } }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 605bfae30292f..609f3618e06c6 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -1302,7 +1302,7 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { class AcceptChangesContribution extends Disposable implements IEditorContribution { - static get(editor: ICodeEditor): AcceptChangesContribution { + static get(editor: ICodeEditor): AcceptChangesContribution | null { return editor.getContribution(AcceptChangesContribution.ID); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts index 699f7be30da50..fc5f376f6b757 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts @@ -417,7 +417,7 @@ type AcceptChangesClassification = { class AcceptChangesContribution extends Disposable implements IEditorContribution { - static get(editor: ICodeEditor): AcceptChangesContribution { + static get(editor: ICodeEditor): AcceptChangesContribution | null { return editor.getContribution(AcceptChangesContribution.ID); } From cb1a286fd77c9b06f6a25ff522e44f89c14771e4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 6 Dec 2021 09:09:11 -0800 Subject: [PATCH 0314/2210] Use a unique authority for desktop webview too Fixes #138409 This fix changes webviews on desktop to always use a unique authority instead of reusing the id passed in. In practice, this change should only effect internal webviews, such as those on extension pages or in getting started, since those were the only webviews that were using a hardcoded id This aligns webviews on desktop with web. --- src/vs/workbench/contrib/webview/browser/webviewElement.ts | 2 +- .../contrib/webview/electron-sandbox/webviewElement.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 6797ecaec0b17..a85f8dd96aa63 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -95,7 +95,7 @@ namespace WebviewState { export class WebviewElement extends Disposable implements IWebview, WebviewFindDelegate { public readonly id: string; - private readonly iframeId: string; + protected readonly iframeId: string; protected get platform(): string { return 'browser'; } diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts index d1318e247ae2b..55ee9927e7628 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts @@ -91,7 +91,7 @@ export class ElectronWebviewElement extends WebviewElement { } protected override get webviewContentEndpoint(): string { - return `${Schemas.vscodeWebview}://${this.id}`; + return `${Schemas.vscodeWebview}://${this.iframeId}`; } /** From 18252333b66c4f7f08fef6a8f67d186fb548d6d0 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 6 Dec 2021 13:02:51 -0500 Subject: [PATCH 0315/2210] Introduce `onDidModelChange` to the group (#137388) * Add aggregate model event * Switch to aggregate model events * Use GroupChangeKind instead * Switch to using GroupChange event * Introduce an onDidModelChange * Move group change to belong to the model * Address comments * editors - for now keep `GroupChangeKind' accessible via groups service * one more * avoid IEditorCloseEvent * further avoid types * simplify events * clean up events * fix reexport issue * Address PR feedback Co-authored-by: Benjamin Pasero --- .../api/browser/mainThreadEditorTabs.ts | 11 +- .../browser/parts/editor/editorGroupView.ts | 104 ++++++-- .../browser/parts/editor/editorPart.ts | 4 +- .../browser/parts/editor/editorsObserver.ts | 4 +- src/vs/workbench/common/editor.ts | 20 ++ .../common/editor/editorGroupModel.ts | 235 ++++++++++++++---- .../files/browser/views/openEditorsView.ts | 4 +- .../browser/notebookEditorServiceImpl.ts | 4 +- .../services/editor/browser/editorService.ts | 22 +- .../editor/common/editorGroupsService.ts | 53 +--- .../services/editor/common/editorService.ts | 3 +- .../test/browser/editorGroupsService.test.ts | 19 +- .../parts/editor/editorGroupModel.test.ts | 188 ++++++++------ .../test/browser/workbenchTestServices.ts | 4 +- 14 files changed, 440 insertions(+), 235 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index 8c78575c99f48..e94747943a23c 100644 --- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -7,12 +7,13 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ExtHostContext, IExtHostEditorTabsShape, IExtHostContext, MainContext, IEditorTabDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { EditorResourceAccessor, IUntypedEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, IUntypedEditorInput, SideBySideEditor, GroupChangeKind } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { isGroupEditorCloseEvent, isGroupEditorMoveEvent, isGroupEditorOpenEvent } from 'vs/workbench/common/editor/editorGroupModel'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { columnToEditorGroup, EditorGroupColumn, editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; -import { GroupChangeKind, GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorsChangeEvent, IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -105,7 +106,7 @@ export class MainThreadEditorTabs { } private _onDidTabOpen(event: IEditorsChangeEvent): void { - if (event.kind !== GroupChangeKind.EDITOR_OPEN || !event.editor || event.editorIndex === undefined) { + if (!isGroupEditorOpenEvent(event)) { return; } if (!this._tabModel.has(event.groupId)) { @@ -124,7 +125,7 @@ export class MainThreadEditorTabs { } private _onDidTabClose(event: IEditorsChangeEvent): void { - if (event.kind !== GroupChangeKind.EDITOR_CLOSE || event.editorIndex === undefined) { + if (!isGroupEditorCloseEvent(event)) { return; } this._tabModel.get(event.groupId)?.splice(event.editorIndex, 1); @@ -137,7 +138,7 @@ export class MainThreadEditorTabs { } private _onDidTabMove(event: IEditorsChangeEvent): void { - if (event.kind !== GroupChangeKind.EDITOR_MOVE || event.editorIndex === undefined || event.oldEditorIndex === undefined) { + if (!isGroupEditorMoveEvent(event)) { return; } const movedTab = this._tabModel.get(event.groupId)?.splice(event.oldEditorIndex, 1); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index d8eca61115fdb..1d9298b16e083 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editorgroupview'; -import { EditorGroupModel, IEditorOpenOptions, ISerializedEditorGroupModel, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; -import { GroupIdentifier, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, EditorResourceAccessor, IEditorMoveEvent, EditorInputCapabilities, IEditorOpenEvent, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, ActiveEditorGroupLockedContext, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions } from 'vs/workbench/common/editor'; +import { EditorGroupModel, IEditorOpenOptions, IGroupChangeEvent, IGroupEditorCloseEvent, IGroupEditorMoveEvent, IGroupEditorOpenEvent, ISerializedEditorGroupModel, isGroupEditorCloseEvent, isGroupEditorMoveEvent, isGroupEditorOpenEvent, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; +import { GroupIdentifier, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, ActiveEditorGroupLockedContext, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupChangeKind } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { Event, Emitter, Relay } from 'vs/base/common/event'; @@ -18,7 +18,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme'; -import { ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ICloseEditorsFilter, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorPanes } from 'vs/workbench/browser/parts/editor/editorPanes'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; @@ -90,6 +90,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private readonly _onDidGroupChange = this._register(new Emitter()); readonly onDidGroupChange = this._onDidGroupChange.event; + private readonly _onDidModelChange = this._register(new Emitter()); + readonly onDidModelChange = this._onDidModelChange.event; + private readonly _onDidOpenEditorFail = this._register(new Emitter()); readonly onDidOpenEditorFail = this._onDidOpenEditorFail.event; @@ -156,9 +159,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (from instanceof EditorGroupView) { this.model = this._register(from.model.clone()); } else if (isSerializedEditorGroupModel(from)) { - this.model = this._register(instantiationService.createInstance(EditorGroupModel, from)); + this.model = this._register(instantiationService.createInstance(EditorGroupModel, from, this._index)); } else { - this.model = this._register(instantiationService.createInstance(EditorGroupModel, undefined)); + this.model = this._register(instantiationService.createInstance(EditorGroupModel, undefined, this._index)); } //#region create() @@ -523,16 +526,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private registerListeners(): void { // Model Events - this._register(this.model.onDidChangeLocked(() => this.onDidChangeGroupLocked())); - this._register(this.model.onDidChangeEditorPinned(editor => this.onDidChangeEditorPinned(editor))); - this._register(this.model.onDidChangeEditorSticky(editor => this.onDidChangeEditorSticky(editor))); - this._register(this.model.onDidMoveEditor(event => this.onDidMoveEditor(event))); - this._register(this.model.onDidOpenEditor(editor => this.onDidOpenEditor(editor))); - this._register(this.model.onDidCloseEditor(editor => this.handleOnDidCloseEditor(editor))); - this._register(this.model.onWillDisposeEditor(editor => this.onWillDisposeEditor(editor))); - this._register(this.model.onDidChangeEditorDirty(editor => this.onDidChangeEditorDirty(editor))); - this._register(this.model.onDidChangeEditorLabel(editor => this.onDidChangeEditorLabel(editor))); - this._register(this.model.onDidChangeEditorCapabilities(editor => this.onDidChangeEditorCapabilities(editor))); + this._register(this.model.onDidModelChange(e => this.onDidGroupModelChange(e))); // Option Changes this._register(this.accessor.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e))); @@ -541,6 +535,59 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._register(this.accessor.onDidVisibilityChange(e => this.onDidVisibilityChange(e))); } + private onDidGroupModelChange(e: IGroupChangeEvent): void { + + // Re-emit to outside + this._onDidModelChange.fire(e); + + // Handle within + + if (e.kind === GroupChangeKind.GROUP_LOCKED) { + this.onDidChangeGroupLocked(); + return; + } + + if (!e.editor) { + return; + } + + switch (e.kind) { + case GroupChangeKind.EDITOR_PIN: + this.onDidChangeEditorPinned(e.editor); + break; + case GroupChangeKind.EDITOR_STICKY: + this.onDidChangeEditorSticky(e.editor); + break; + case GroupChangeKind.EDITOR_MOVE: + if (isGroupEditorMoveEvent(e)) { + this.onDidMoveEditor(e.editor, e.oldEditorIndex, e.editorIndex); + } + break; + case GroupChangeKind.EDITOR_OPEN: + if (isGroupEditorOpenEvent(e)) { + this.onDidOpenEditor(e.editor, e.editorIndex); + } + break; + case GroupChangeKind.EDITOR_CLOSE: + if (isGroupEditorCloseEvent(e)) { + this.handleOnDidCloseEditor(e.editor, e.editorIndex, e.context, e.sticky); + } + break; + case GroupChangeKind.EDITOR_WILL_DISPOSE: + this.onWillDisposeEditor(e.editor); + break; + case GroupChangeKind.EDITOR_DIRTY: + this.onDidChangeEditorDirty(e.editor); + break; + case GroupChangeKind.EDITOR_LABEL: + this.onDidChangeEditorLabel(e.editor); + break; + case GroupChangeKind.EDITOR_CAPABILITIES: + this.onDidChangeEditorCapabilities(e.editor); + break; + } + } + private onDidChangeGroupLocked(): void { this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_LOCKED }); } @@ -553,11 +600,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_STICKY, editor }); } - private onDidMoveEditor({ editor, index, newIndex }: IEditorMoveEvent): void { - this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_MOVE, editor, oldEditorIndex: index, editorIndex: newIndex }); + private onDidMoveEditor(editor: EditorInput, oldEditorIndex: number, editorIndex: number): void { + const event: IGroupEditorMoveEvent = { kind: GroupChangeKind.EDITOR_MOVE, editor, oldEditorIndex, editorIndex }; + this._onDidGroupChange.fire(event); } - private onDidOpenEditor({ editor, index }: IEditorOpenEvent): void { + private onDidOpenEditor(editor: EditorInput, editorIndex: number): void { /* __GDPR__ "editorOpened" : { @@ -572,16 +620,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.updateContainer(); // Event - this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_OPEN, editor, editorIndex: index }); + const event: IGroupEditorOpenEvent = { kind: GroupChangeKind.EDITOR_OPEN, editor, editorIndex }; + this._onDidGroupChange.fire(event); } - private handleOnDidCloseEditor(event: IEditorCloseEvent): void { + private handleOnDidCloseEditor(editor: EditorInput, editorIndex: number, context: EditorCloseContext, sticky: boolean): void { // Before close - this._onWillCloseEditor.fire(event); + this._onWillCloseEditor.fire({ groupId: this.id, editor, context, index: editorIndex, sticky }); // Handle event - const editor = event.editor; const editorsToClose: EditorInput[] = [editor]; // Include both sides of side by side editors when being closed @@ -606,14 +654,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { ] } */ - this.telemetryService.publicLog('editorClosed', this.toEditorTelemetryDescriptor(event.editor)); + this.telemetryService.publicLog('editorClosed', this.toEditorTelemetryDescriptor(editor)); // Update container this.updateContainer(); // Event - this._onDidCloseEditor.fire(event); - this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_CLOSE, editor, editorIndex: event.index }); + this._onDidCloseEditor.fire({ groupId: this.id, editor, context, index: editorIndex, sticky }); + const event: IGroupEditorCloseEvent = { kind: GroupChangeKind.EDITOR_CLOSE, editor, editorIndex, context, sticky }; + this._onDidGroupChange.fire(event); } private canDispose(editor: EditorInput): boolean { @@ -794,6 +843,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (this._index !== newIndex) { this._index = newIndex; this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_INDEX }); + this.model.setIndex(this._index); } } @@ -1439,7 +1489,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Update model let index: number | undefined = undefined; if (editorToClose) { - index = this.model.closeEditor(editorToClose, internalOptions?.context)?.index; + index = this.model.closeEditor(editorToClose, internalOptions?.context)?.editorIndex; } // Open next active if there are more to show @@ -1509,7 +1559,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private doCloseInactiveEditor(editor: EditorInput, internalOptions?: IInternalEditorCloseOptions): number | undefined { // Update model - return this.model.closeEditor(editor, internalOptions?.context)?.index; + return this.model.closeEditor(editor, internalOptions?.context)?.editorIndex; } private async handleDirtyClosing(editors: EditorInput[]): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index efcada70fecfe..6ff9139d65827 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -8,10 +8,10 @@ import { Part } from 'vs/workbench/browser/part'; import { Dimension, isAncestor, $, EventHelper, addDisposableGenericMouseDownListner } from 'vs/base/browser/dom'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument, IEditorGroupsService, IEditorSideGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, GroupsOrder, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument, IEditorGroupsService, IEditorSideGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; -import { GroupIdentifier, EditorInputWithOptions, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; +import { GroupIdentifier, EditorInputWithOptions, IEditorPartOptions, IEditorPartOptionsChangeEvent, GroupChangeKind } from 'vs/workbench/common/editor'; import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme'; import { distinct, coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor'; diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index d206ce34b9972..081963b3ecc5c 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEditorFactoryRegistry, IEditorIdentifier, GroupIdentifier, EditorExtensions, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorFactoryRegistry, IEditorIdentifier, GroupIdentifier, EditorExtensions, IEditorPartOptionsChangeEvent, EditorsOrder, GroupChangeKind } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; -import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { coalesce } from 'vs/base/common/arrays'; import { LinkedMap, Touch, ResourceMap } from 'vs/base/common/map'; import { equals } from 'vs/base/common/objects'; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index f9b267d7d89a6..56c58f09059e3 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -823,6 +823,26 @@ export interface IEditorOpenEvent extends IEditorIdentifier { export type GroupIdentifier = number; +export const enum GroupChangeKind { + + /* Group Changes */ + GROUP_ACTIVE, + GROUP_INDEX, + GROUP_LOCKED, + + /* Editor Changes */ + EDITOR_OPEN, + EDITOR_CLOSE, + EDITOR_MOVE, + EDITOR_ACTIVE, + EDITOR_LABEL, + EDITOR_CAPABILITIES, + EDITOR_PIN, + EDITOR_STICKY, + EDITOR_DIRTY, + EDITOR_WILL_DISPOSE +} + export interface IWorkbenchEditorConfiguration { workbench?: { editor?: IEditorPartConfiguration, diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 7a2857877b523..42d77d55f8616 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { IEditorFactoryRegistry, GroupIdentifier, EditorsOrder, EditorExtensions, IUntypedEditorInput, SideBySideEditor, IEditorMoveEvent, IEditorOpenEvent, EditorCloseContext, IEditorCloseEvent, IMatchEditorOptions } from 'vs/workbench/common/editor'; +import { IEditorFactoryRegistry, GroupIdentifier, EditorsOrder, EditorExtensions, IUntypedEditorInput, SideBySideEditor, EditorCloseContext, IMatchEditorOptions, GroupChangeKind } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -53,44 +53,125 @@ export function isSerializedEditorGroupModel(group?: unknown): group is ISeriali return !!(candidate && typeof candidate === 'object' && Array.isArray(candidate.editors) && Array.isArray(candidate.mru)); } -export class EditorGroupModel extends Disposable { +export interface IMatchOptions { - private static IDS = 0; + /** + * Whether to consider a side by side editor as matching. + * By default, side by side editors will not be considered + * as matching, even if the editor is opened in one of the sides. + */ + readonly supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH; - //#region events + /** + * Only consider an editor to match when the + * `candidate === editor` but not when + * `candidate.matches(editor)`. + */ + readonly strictEquals?: boolean; +} + +export interface IGroupChangeEvent { + + /** + * The kind of change that occured in the group. + */ + readonly kind: GroupChangeKind; + + /** + * Only applies when editors change providing + * access to the editor the event is about. + */ + readonly editor?: EditorInput; +} + +export interface IGroupEditorChangeEvent extends IGroupChangeEvent { + readonly editor: EditorInput; +} + +export interface IGroupEditorOpenEvent extends IGroupEditorChangeEvent { + + readonly kind: GroupChangeKind.EDITOR_OPEN; + + /** + * Identifies the index of the editor in the group. + */ + readonly editorIndex: number; +} + +export function isGroupEditorOpenEvent(e: IGroupChangeEvent): e is IGroupEditorOpenEvent { + const candidate = e as IGroupEditorOpenEvent; + + return candidate.kind === GroupChangeKind.EDITOR_OPEN && candidate.editorIndex !== undefined; +} + +export interface IGroupEditorMoveEvent extends IGroupEditorChangeEvent { + + readonly kind: GroupChangeKind.EDITOR_MOVE; - private readonly _onDidChangeLocked = this._register(new Emitter()); - readonly onDidChangeLocked = this._onDidChangeLocked.event; + /** + * Identifies the index of the editor in the group. + */ + readonly editorIndex: number; - private readonly _onDidActivateEditor = this._register(new Emitter()); - readonly onDidActivateEditor = this._onDidActivateEditor.event; + /** + * Signifies the index the editor is moving from. + * `editorIndex` will contain the index the editor + * is moving to. + */ + readonly oldEditorIndex: number; +} + +export function isGroupEditorMoveEvent(e: IGroupChangeEvent): e is IGroupEditorMoveEvent { + const candidate = e as IGroupEditorMoveEvent; - private readonly _onDidOpenEditor = this._register(new Emitter()); - readonly onDidOpenEditor = this._onDidOpenEditor.event; + return candidate.kind === GroupChangeKind.EDITOR_MOVE && candidate.editorIndex !== undefined && candidate.oldEditorIndex !== undefined; +} - private readonly _onDidCloseEditor = this._register(new Emitter()); - readonly onDidCloseEditor = this._onDidCloseEditor.event; +export interface IGroupEditorCloseEvent extends IGroupEditorChangeEvent { - private readonly _onWillDisposeEditor = this._register(new Emitter()); - readonly onWillDisposeEditor = this._onWillDisposeEditor.event; + readonly kind: GroupChangeKind.EDITOR_CLOSE; - private readonly _onDidChangeEditorDirty = this._register(new Emitter()); - readonly onDidChangeEditorDirty = this._onDidChangeEditorDirty.event; + /** + * Identifies the index of the editor in the group. + */ + readonly editorIndex: number; - private readonly _onDidChangeEditorLabel = this._register(new Emitter()); - readonly onDidChangeEditorLabel = this._onDidChangeEditorLabel.event; + /** + * Signifies the context in which the editor + * is being closed. This allows for understanding + * if a replace or reopen is occuring + */ + readonly context: EditorCloseContext; - private readonly _onDidChangeEditorCapabilities = this._register(new Emitter()); - readonly onDidChangeEditorCapabilities = this._onDidChangeEditorCapabilities.event; + /** + * Signifies whether or not the closed editor was + * sticky. This is necessary becasue state is lost + * after closing. + */ + readonly sticky: boolean; +} - private readonly _onDidMoveEditor = this._register(new Emitter()); - readonly onDidMoveEditor = this._onDidMoveEditor.event; +export function isGroupEditorCloseEvent(e: IGroupChangeEvent): e is IGroupEditorCloseEvent { + const candidate = e as IGroupEditorCloseEvent; - private readonly _onDidChangeEditorPinned = this._register(new Emitter()); - readonly onDidChangeEditorPinned = this._onDidChangeEditorPinned.event; + return candidate.kind === GroupChangeKind.EDITOR_CLOSE && candidate.editorIndex !== undefined && candidate.context !== undefined && candidate.sticky !== undefined; +} - private readonly _onDidChangeEditorSticky = this._register(new Emitter()); - readonly onDidChangeEditorSticky = this._onDidChangeEditorSticky.event; +interface IEditorCloseResult { + readonly editor: EditorInput; + readonly context: EditorCloseContext; + readonly editorIndex: number; + readonly sticky: boolean; +} + +export class EditorGroupModel extends Disposable { + + private static IDS = 0; + + //#region events + + private readonly _onDidModelChange = this._register(new Emitter()); + readonly onDidModelChange = this._onDidModelChange.event; //#endregion @@ -111,6 +192,7 @@ export class EditorGroupModel extends Disposable { constructor( labelOrSerializedGroup: ISerializedEditorGroupModel | undefined, + private index: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService ) { @@ -269,7 +351,12 @@ export class EditorGroupModel extends Disposable { this.registerEditorListeners(newEditor); // Event - this._onDidOpenEditor.fire({ editor: newEditor, groupId: this.id, index: targetIndex }); + const event: IGroupEditorOpenEvent = { + kind: GroupChangeKind.EDITOR_OPEN, + editor: newEditor, + editorIndex: targetIndex + }; + this._onDidModelChange.fire(event); // Handle active if (makeActive) { @@ -320,59 +407,79 @@ export class EditorGroupModel extends Disposable { // Re-emit disposal of editor input as our own event listeners.add(Event.once(editor.onWillDispose)(() => { if (this.indexOf(editor) >= 0) { - this._onWillDisposeEditor.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_WILL_DISPOSE, + editor + }); } })); // Re-Emit dirty state changes listeners.add(editor.onDidChangeDirty(() => { - this._onDidChangeEditorDirty.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_DIRTY, + editor + }); })); // Re-Emit label changes listeners.add(editor.onDidChangeLabel(() => { - this._onDidChangeEditorLabel.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_LABEL, + editor + }); })); // Re-Emit capability changes listeners.add(editor.onDidChangeCapabilities(() => { - this._onDidChangeEditorCapabilities.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_CAPABILITIES, + editor + }); })); // Clean up dispose listeners once the editor gets closed - listeners.add(this.onDidCloseEditor(event => { - if (event.editor.matches(editor)) { + listeners.add(this.onDidModelChange(event => { + if (event.kind === GroupChangeKind.EDITOR_CLOSE && event.editor?.matches(editor)) { dispose(listeners); } })); } private replaceEditor(toReplace: EditorInput, replaceWith: EditorInput, replaceIndex: number, openNext = true): void { - const event = this.doCloseEditor(toReplace, EditorCloseContext.REPLACE, openNext); // optimization to prevent multiple setActive() in one call + const closeResult = this.doCloseEditor(toReplace, EditorCloseContext.REPLACE, openNext); // optimization to prevent multiple setActive() in one call // We want to first add the new editor into our model before emitting the close event because // firing the close event can trigger a dispose on the same editor that is now being added. // This can lead into opening a disposed editor which is not what we want. this.splice(replaceIndex, false, replaceWith); - if (event) { - this._onDidCloseEditor.fire(event); + if (closeResult) { + const event: IGroupEditorCloseEvent = { + kind: GroupChangeKind.EDITOR_CLOSE, + ...closeResult + }; + this._onDidModelChange.fire(event); } } - closeEditor(candidate: EditorInput, context = EditorCloseContext.UNKNOWN, openNext = true): IEditorCloseEvent | undefined { - const event = this.doCloseEditor(candidate, context, openNext); + closeEditor(candidate: EditorInput, context = EditorCloseContext.UNKNOWN, openNext = true): IEditorCloseResult | undefined { + const closeResult = this.doCloseEditor(candidate, context, openNext); - if (event) { - this._onDidCloseEditor.fire(event); + if (closeResult) { + const event: IGroupEditorCloseEvent = { + kind: GroupChangeKind.EDITOR_CLOSE, + ...closeResult + }; + this._onDidModelChange.fire(event); - return event; + return closeResult; } return undefined; } - private doCloseEditor(candidate: EditorInput, context: EditorCloseContext, openNext: boolean): IEditorCloseEvent | undefined { + private doCloseEditor(candidate: EditorInput, context: EditorCloseContext, openNext: boolean): IEditorCloseResult | undefined { const index = this.indexOf(candidate); if (index === -1) { return undefined; // not found @@ -415,7 +522,7 @@ export class EditorGroupModel extends Disposable { this.splice(index, true); // Event - return { editor, sticky, index, groupId: this.id, context }; + return { editor, sticky, editorIndex: index, context }; } moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined { @@ -449,7 +556,13 @@ export class EditorGroupModel extends Disposable { this.editors.splice(toIndex, 0, editor); // Event - this._onDidMoveEditor.fire({ editor, groupId: this.id, index, newIndex: toIndex, target: this.id }); + const event: IGroupEditorMoveEvent = { + kind: GroupChangeKind.EDITOR_MOVE, + editor, + oldEditorIndex: index, + editorIndex: toIndex, + }; + this._onDidModelChange.fire(event); return editor; } @@ -480,7 +593,15 @@ export class EditorGroupModel extends Disposable { this.mru.unshift(editor); // Event - this._onDidActivateEditor.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_ACTIVE, + editor + }); + } + + setIndex(index: number) { + this.index = index; + this._onDidModelChange.fire({ kind: GroupChangeKind.GROUP_INDEX }); } pin(candidate: EditorInput): EditorInput | undefined { @@ -505,7 +626,10 @@ export class EditorGroupModel extends Disposable { this.preview = null; // Event - this._onDidChangeEditorPinned.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_PIN, + editor + }); } unpin(candidate: EditorInput): EditorInput | undefined { @@ -531,7 +655,10 @@ export class EditorGroupModel extends Disposable { this.preview = editor; // Event - this._onDidChangeEditorPinned.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_PIN, + editor + }); // Close old preview editor if any if (oldPreview) { @@ -578,7 +705,10 @@ export class EditorGroupModel extends Disposable { this.sticky++; // Event - this._onDidChangeEditorSticky.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_STICKY, + editor + }); } unstick(candidate: EditorInput): EditorInput | undefined { @@ -606,7 +736,10 @@ export class EditorGroupModel extends Disposable { this.sticky--; // Event - this._onDidChangeEditorSticky.fire(editor); + this._onDidModelChange.fire({ + kind: GroupChangeKind.EDITOR_STICKY, + editor + }); } isSticky(candidateOrIndex: EditorInput | number): boolean { @@ -757,12 +890,12 @@ export class EditorGroupModel extends Disposable { if (this.isLocked !== locked) { this.locked = locked; - this._onDidChangeLocked.fire(); + this._onDidModelChange.fire({ kind: GroupChangeKind.GROUP_LOCKED }); } } clone(): EditorGroupModel { - const clone = this.instantiationService.createInstance(EditorGroupModel, undefined); + const clone = this.instantiationService.createInstance(EditorGroupModel, undefined, this.index); // Copy over group properties clone.editors = this.editors.slice(0); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index ddaee4466bc83..76ad752292944 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -10,10 +10,10 @@ import { IAction, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExe import * as dom from 'vs/base/browser/dom'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { Verbosity, EditorResourceAccessor, SideBySideEditor, EditorInputCapabilities, IEditorIdentifier } from 'vs/workbench/common/editor'; +import { Verbosity, EditorResourceAccessor, SideBySideEditor, EditorInputCapabilities, IEditorIdentifier, GroupChangeKind } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration, OpenEditor } from 'vs/workbench/contrib/files/common/files'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts index 88c1bc11cfd53..c0658f846bde1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts @@ -6,14 +6,14 @@ import { ResourceMap } from 'vs/base/common/map'; import { getDefaultNotebookCreationOptions, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupChangeKind } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookEditor, INotebookEditorCreationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Emitter } from 'vs/base/common/event'; import { INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { GroupIdentifier } from 'vs/workbench/common/editor'; +import { GroupIdentifier, GroupChangeKind } from 'vs/workbench/common/editor'; export class NotebookEditorWidgetService implements INotebookEditorService { diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 07fb157abf86a..e02323045827e 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,7 +5,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IResourceEditorInput, IEditorOptions, EditorActivation, EditorResolution, IResourceEditorInputIdentifier, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; -import { SideBySideEditor, IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, EditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, EditorInputCapabilities, isResourceDiffEditorInput, IUntypedEditorInput, isResourceEditorInput, isEditorInput, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; +import { SideBySideEditor, IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, EditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, EditorInputCapabilities, isResourceDiffEditorInput, IUntypedEditorInput, isResourceEditorInput, isEditorInput, isEditorInputWithOptionsAndGroup, GroupChangeKind } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { ResourceMap } from 'vs/base/common/map'; @@ -14,7 +14,7 @@ import { Event, Emitter, MicrotaskEmitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, isEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, isEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IUntypedEditorReplacement, IEditorService, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorsOptions, PreferredGroup, isPreferredGroup, IEditorsChangeEvent } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; @@ -147,18 +147,14 @@ export class EditorService extends Disposable implements EditorServiceImpl { private registerGroupListeners(group: IEditorGroupView): void { const groupDisposables = new DisposableStore(); + groupDisposables.add(group.onDidModelChange(e => this._onDidEditorsChange.fire([{ groupId: group.id, ...e }]))); + + // Need to separatly listen to the group change for things like active editor changing + // as this doesn't always change the model (This could be a bug that needs more investigation) groupDisposables.add(group.onDidGroupChange(e => { - switch (e.kind) { - case GroupChangeKind.EDITOR_ACTIVE: - if (group.activeEditor) { - this._onDidEditorsChange.fire([{ groupId: group.id, editor: group.activeEditor, kind: GroupChangeKind.EDITOR_ACTIVE }]); - } - this.handleActiveEditorChange(group); - this._onDidVisibleEditorsChange.fire(); - break; - default: - this._onDidEditorsChange.fire([{ groupId: group.id, ...e }]); - break; + if (e.kind === GroupChangeKind.EDITOR_ACTIVE) { + this.handleActiveEditorChange(group); + this._onDidVisibleEditorsChange.fire(); } })); diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 1f1950465d837..e86eb2357d0cf 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -13,6 +13,7 @@ import { IDimension } from 'vs/editor/common/editorCommon'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; +import { IGroupChangeEvent } from 'vs/workbench/common/editor/editorGroupModel'; export const IEditorGroupsService = createDecorator('editorGroupsService'); @@ -395,53 +396,6 @@ export interface IEditorGroupsService { enforcePartOptions(options: IEditorPartOptions): IDisposable; } -export const enum GroupChangeKind { - - /* Group Changes */ - GROUP_ACTIVE, - GROUP_INDEX, - GROUP_LOCKED, - - /* Editor Changes */ - EDITOR_OPEN, - EDITOR_CLOSE, - EDITOR_MOVE, - EDITOR_ACTIVE, - EDITOR_LABEL, - EDITOR_CAPABILITIES, - EDITOR_PIN, - EDITOR_STICKY, - EDITOR_DIRTY -} - -export interface IGroupChangeEvent { - - /** - * The kind of change that occured in the group. - */ - kind: GroupChangeKind; - - /** - * Only applies when editors change providing - * access to the editor the event is about. - */ - editor?: EditorInput; - - /** - * Only applies when an editor opens, closes - * or is moved. Identifies the index of the - * editor in the group. - */ - editorIndex?: number; - - /** - * For `EDITOR_MOVE` only: Signifies the index the - * editor is moving from. `editorIndex` will contain - * the index the editor is moving to. - */ - oldEditorIndex?: number; -} - export const enum OpenEditorContext { NEW_EDITOR = 1, MOVE_EDITOR = 2, @@ -455,6 +409,11 @@ export interface IEditorGroup { */ readonly onDidGroupChange: Event; + /** + * An event which fires whenever the underlying group model changes. + */ + readonly onDidModelChange: Event; + /** * An event that is fired when the group gets disposed. */ diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 77867ed511b0b..598580245e2a3 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -9,8 +9,9 @@ import { IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResour import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { Event } from 'vs/base/common/event'; import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IEditorGroup, IEditorReplacement, IGroupChangeEvent, isEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorReplacement, isEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { URI } from 'vs/base/common/uri'; +import { IGroupChangeEvent } from 'vs/workbench/common/editor/editorGroupModel'; export const IEditorService = createDecorator('editorService'); diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index e4de03f00cf8e..11673e5b7eed2 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -5,8 +5,8 @@ import * as assert from 'assert'; import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, ITestInstantiationService, TestServiceAccessor, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices'; -import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, GroupLocation, isEditorGroup, IEditorGroupsService, IGroupChangeEvent } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { CloseDirection, IEditorPartOptions, EditorsOrder, EditorInputCapabilities } from 'vs/workbench/common/editor'; +import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupLocation, isEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { CloseDirection, IEditorPartOptions, EditorsOrder, EditorInputCapabilities, GroupChangeKind } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -15,6 +15,7 @@ import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; +import { IGroupChangeEvent, IGroupEditorMoveEvent, IGroupEditorOpenEvent } from 'vs/workbench/common/editor/editorGroupModel'; suite('EditorGroupsService', () => { @@ -454,8 +455,8 @@ suite('EditorGroupsService', () => { assert.strictEqual(group.count, 2); assert.strictEqual(editorCapabilitiesCounter, 0); assert.strictEqual(editorDidOpenCounter, 2); - assert.strictEqual(editorOpenEvents[0].editorIndex, 0); - assert.strictEqual(editorOpenEvents[1].editorIndex, 1); + assert.strictEqual((editorOpenEvents[0] as IGroupEditorOpenEvent).editorIndex, 0); + assert.strictEqual((editorOpenEvents[1] as IGroupEditorOpenEvent).editorIndex, 1); assert.strictEqual(editorOpenEvents[0].editor, input); assert.strictEqual(editorOpenEvents[1].editor, inputInactive); assert.strictEqual(activeEditorChangeCounter, 1); @@ -495,7 +496,7 @@ suite('EditorGroupsService', () => { assert.strictEqual(activeEditorChangeCounter, 3); assert.strictEqual(editorCloseCounter, 1); - assert.strictEqual(editorCloseEvents[0].editorIndex, 1); + assert.strictEqual((editorCloseEvents[0] as IGroupEditorOpenEvent).editorIndex, 1); assert.strictEqual(editorCloseEvents[0].editor, inputInactive); assert.strictEqual(editorCloseCounter1, 1); assert.strictEqual(editorWillCloseCounter, 1); @@ -952,16 +953,16 @@ suite('EditorGroupsService', () => { assert.strictEqual(group.getEditorByIndex(1), inputInactive); group.moveEditor(inputInactive, group, { index: 0 }); assert.strictEqual(moveEvents.length, 1); - assert.strictEqual(moveEvents[0].editorIndex, 0); - assert.strictEqual(moveEvents[0].oldEditorIndex, 1); + assert.strictEqual((moveEvents[0] as IGroupEditorOpenEvent).editorIndex, 0); + assert.strictEqual((moveEvents[0] as IGroupEditorMoveEvent).oldEditorIndex, 1); assert.strictEqual(moveEvents[0].editor, inputInactive); assert.strictEqual(group.getEditorByIndex(0), inputInactive); assert.strictEqual(group.getEditorByIndex(1), input); group.moveEditors([{ editor: inputInactive, options: { index: 1 } }], group); assert.strictEqual(moveEvents.length, 2); - assert.strictEqual(moveEvents[1].editorIndex, 1); - assert.strictEqual(moveEvents[1].oldEditorIndex, 0); + assert.strictEqual((moveEvents[1] as IGroupEditorOpenEvent).editorIndex, 1); + assert.strictEqual((moveEvents[1] as IGroupEditorMoveEvent).oldEditorIndex, 0); assert.strictEqual(moveEvents[1].editor, inputInactive); assert.strictEqual(group.getEditorByIndex(0), input); assert.strictEqual(group.getEditorByIndex(1), inputInactive); diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts index 756a415da6249..011bd5ec8408c 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorGroupModel, ISerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; -import { EditorExtensions, IEditorFactoryRegistry, IFileEditorInput, IEditorSerializer, CloseDirection, EditorsOrder, IResourceDiffEditorInput, IResourceSideBySideEditorInput, SideBySideEditor, EditorCloseContext, IEditorCloseEvent, IEditorOpenEvent, IEditorMoveEvent } from 'vs/workbench/common/editor'; +import { EditorGroupModel, ISerializedEditorGroupModel, isGroupEditorCloseEvent, isGroupEditorMoveEvent, isGroupEditorOpenEvent } from 'vs/workbench/common/editor/editorGroupModel'; +import { EditorExtensions, IEditorFactoryRegistry, IFileEditorInput, IEditorSerializer, CloseDirection, EditorsOrder, IResourceDiffEditorInput, IResourceSideBySideEditorInput, SideBySideEditor, EditorCloseContext, IEditorCloseEvent, IEditorOpenEvent, IEditorMoveEvent, GroupChangeKind } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { TestLifecycleService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -42,8 +42,8 @@ suite('EditorGroupModel', () => { return inst; } - function createEditorGroupModel(serialized?: ISerializedEditorGroupModel): EditorGroupModel { - return inst().createInstance(EditorGroupModel, serialized); + function createEditorGroupModel(serialized: ISerializedEditorGroupModel | undefined, index: number): EditorGroupModel { + return inst().createInstance(EditorGroupModel, serialized, index); } function closeAllEditors(group: EditorGroupModel): void { @@ -105,14 +105,44 @@ suite('EditorGroupModel', () => { disposed: [] }; - group.onDidChangeLocked(() => groupEvents.locked.push(group.id)); - group.onDidOpenEditor(e => groupEvents.opened.push(e)); - group.onDidCloseEditor(e => groupEvents.closed.push(e)); - group.onDidActivateEditor(e => groupEvents.activated.push(e)); - group.onDidChangeEditorPinned(e => group.isPinned(e) ? groupEvents.pinned.push(e) : groupEvents.unpinned.push(e)); - group.onDidChangeEditorSticky(e => group.isSticky(e) ? groupEvents.sticky.push(e) : groupEvents.unsticky.push(e)); - group.onDidMoveEditor(e => groupEvents.moved.push(e)); - group.onWillDisposeEditor(e => groupEvents.disposed.push(e)); + group.onDidModelChange(e => { + if (e.kind === GroupChangeKind.GROUP_LOCKED) { + groupEvents.locked.push(group.id); + return; + } + if (!e.editor) { + return; + } + switch (e.kind) { + case GroupChangeKind.EDITOR_OPEN: + if (isGroupEditorOpenEvent(e)) { + groupEvents.opened.push({ editor: e.editor, index: e.editorIndex, groupId: group.id }); + } + break; + case GroupChangeKind.EDITOR_CLOSE: + if (isGroupEditorCloseEvent(e)) { + groupEvents.closed.push({ editor: e.editor, index: e.editorIndex, groupId: group.id, context: e.context, sticky: e.sticky }); + } + break; + case GroupChangeKind.EDITOR_ACTIVE: + groupEvents.activated.push(e.editor); + break; + case GroupChangeKind.EDITOR_PIN: + group.isPinned(e.editor) ? groupEvents.pinned.push(e.editor) : groupEvents.unpinned.push(e.editor); + break; + case GroupChangeKind.EDITOR_STICKY: + group.isSticky(e.editor) ? groupEvents.sticky.push(e.editor) : groupEvents.unsticky.push(e.editor); + break; + case GroupChangeKind.EDITOR_MOVE: + if (isGroupEditorMoveEvent(e)) { + groupEvents.moved.push({ editor: e.editor, index: e.oldEditorIndex, newIndex: e.editorIndex, target: group.id, groupId: group.id }); + } + break; + case GroupChangeKind.EDITOR_WILL_DISPOSE: + groupEvents.disposed.push(e.editor); + break; + } + }); return groupEvents; } @@ -252,7 +282,7 @@ suite('EditorGroupModel', () => { }); test('Clone Group', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = input() as TestEditorInput; const input2 = input(); @@ -278,7 +308,11 @@ suite('EditorGroupModel', () => { assert.strictEqual(clone.isLocked, false); // locking does not clone over let didEditorLabelChange = false; - const toDispose = clone.onDidChangeEditorLabel(() => didEditorLabelChange = true); + const toDispose = clone.onDidModelChange((e) => { + if (e.kind === GroupChangeKind.EDITOR_LABEL) { + didEditorLabelChange = true; + } + }); input1.setLabel(); assert.ok(didEditorLabelChange); @@ -298,7 +332,7 @@ suite('EditorGroupModel', () => { }); test('isActive - untyped', () => { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input = new TestFileEditorInput('testInput', URI.file('fake')); const input2 = new TestFileEditorInput('testInput2', URI.file('fake2')); const untypedInput = { resource: URI.file('/fake'), options: { override: 'testInput' } }; @@ -315,7 +349,7 @@ suite('EditorGroupModel', () => { test('openEditor - prefers existing side by side editor if same', () => { const instantiationService = workbenchInstantiationService(undefined, disposables); - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = new TestFileEditorInput('testInput', URI.file('fake1')); const input2 = new TestFileEditorInput('testInput', URI.file('fake2')); @@ -343,7 +377,7 @@ suite('EditorGroupModel', () => { test('indexOf() - prefers direct matching editor over side by side matching one', () => { const instantiationService = workbenchInstantiationService(undefined, disposables); - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = new TestFileEditorInput('testInput', URI.file('fake1')); const sideBySideInput = instantiationService.createInstance(SideBySideEditorInput, undefined, undefined, input1, input1); @@ -361,7 +395,7 @@ suite('EditorGroupModel', () => { }); test('contains() - untyped', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const instantiationService = workbenchInstantiationService(undefined, disposables); const input1 = input('input1', false, URI.file('/input1')); @@ -482,7 +516,7 @@ suite('EditorGroupModel', () => { }); test('contains()', () => { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const instantiationService = workbenchInstantiationService(undefined, disposables); const input1 = input(); @@ -595,7 +629,7 @@ suite('EditorGroupModel', () => { test('group serialization', function () { inst().invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = input(); const input2 = input(); @@ -607,7 +641,7 @@ suite('EditorGroupModel', () => { group.openEditor(input2, { pinned: true, active: true }); group.openEditor(input3, { pinned: false, active: true }); - let deserialized = createEditorGroupModel(group.serialize()); + let deserialized = createEditorGroupModel(group.serialize(), 0); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 3); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 3); @@ -620,7 +654,7 @@ suite('EditorGroupModel', () => { // Case 2: inputs cannot be serialized TestEditorInputSerializer.disableSerialize = true; - deserialized = createEditorGroupModel(group.serialize()); + deserialized = createEditorGroupModel(group.serialize(), 0); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 0); @@ -630,7 +664,7 @@ suite('EditorGroupModel', () => { TestEditorInputSerializer.disableSerialize = false; TestEditorInputSerializer.disableDeserialize = true; - deserialized = createEditorGroupModel(group.serialize()); + deserialized = createEditorGroupModel(group.serialize(), 0); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 0); @@ -639,7 +673,7 @@ suite('EditorGroupModel', () => { test('group serialization (sticky editor)', function () { inst().invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = input(); const input2 = input(); @@ -654,7 +688,7 @@ suite('EditorGroupModel', () => { group.stick(input2); assert.ok(group.isSticky(input2)); - let deserialized = createEditorGroupModel(group.serialize()); + let deserialized = createEditorGroupModel(group.serialize(), 0); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 3); @@ -673,7 +707,7 @@ suite('EditorGroupModel', () => { // Case 2: inputs cannot be serialized TestEditorInputSerializer.disableSerialize = true; - deserialized = createEditorGroupModel(group.serialize()); + deserialized = createEditorGroupModel(group.serialize(), 0); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.stickyCount, 0); @@ -684,7 +718,7 @@ suite('EditorGroupModel', () => { TestEditorInputSerializer.disableSerialize = false; TestEditorInputSerializer.disableDeserialize = true; - deserialized = createEditorGroupModel(group.serialize()); + deserialized = createEditorGroupModel(group.serialize(), 0); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.stickyCount, 0); @@ -693,7 +727,7 @@ suite('EditorGroupModel', () => { }); test('group serialization (locked group)', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const events = groupListener(group); @@ -711,23 +745,23 @@ suite('EditorGroupModel', () => { }); test('locked group', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); group.lock(true); - let deserialized = createEditorGroupModel(group.serialize()); + let deserialized = createEditorGroupModel(group.serialize(), 0); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.isLocked, true); group.lock(false); - deserialized = createEditorGroupModel(group.serialize()); + deserialized = createEditorGroupModel(group.serialize(), 0); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.isLocked, false); }); test('One Editor', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const events = groupListener(group); assert.strictEqual(group.count, 0); @@ -754,7 +788,7 @@ suite('EditorGroupModel', () => { let index = group.indexOf(input1); let event = group.closeEditor(input1, EditorCloseContext.UNPIN); assert.strictEqual(event?.editor, input1); - assert.strictEqual(event?.index, index); + assert.strictEqual(event?.editorIndex, index); assert.strictEqual(group.count, 0); assert.strictEqual(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); assert.strictEqual(group.activeEditor, null); @@ -844,7 +878,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Pinned and Active', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const events = groupListener(group); const input1 = input('1'); @@ -918,7 +952,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Preview editor moves to the side of the active one', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = input(); const input2 = input(); @@ -971,7 +1005,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Pinned and Not Active', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = input(); const input2 = input(); @@ -1003,7 +1037,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Preview gets overwritten', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const events = groupListener(group); const input1 = input(); @@ -1036,7 +1070,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - set active', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const events = groupListener(group); const input1 = input(); @@ -1071,7 +1105,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - pin and unpin', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const events = groupListener(group); const input1 = input(); @@ -1120,7 +1154,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - closing picks next from MRU list', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const events = groupListener(group); const input1 = input(); @@ -1227,7 +1261,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - move editor', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const events = groupListener(group); const input1 = input(); @@ -1298,8 +1332,8 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - move editor across groups', function () { - const group1 = createEditorGroupModel(); - const group2 = createEditorGroupModel(); + const group1 = createEditorGroupModel(undefined, 0); + const group2 = createEditorGroupModel(undefined, 0); const g1_input1 = input(); const g1_input2 = input(); @@ -1320,8 +1354,8 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - move editor across groups (input already exists in group 1)', function () { - const group1 = createEditorGroupModel(); - const group2 = createEditorGroupModel(); + const group1 = createEditorGroupModel(undefined, 0); + const group2 = createEditorGroupModel(undefined, 0); const g1_input1 = input(); const g1_input2 = input(); @@ -1344,7 +1378,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Pinned & Non Active', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = input(); group.openEditor(input1); @@ -1375,7 +1409,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Close Others, Close Left, Close Right', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = input(); const input2 = input(); @@ -1430,7 +1464,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - real user example', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); // [] -> /index.html/ const indexHtml = input('index.html'); @@ -1564,7 +1598,7 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group = createEditorGroupModel(); + let group = createEditorGroupModel(undefined, 0); const input1 = input(); group.openEditor(input1); @@ -1598,7 +1632,7 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group1 = createEditorGroupModel(); + let group1 = createEditorGroupModel(undefined, 0); const g1_input1 = input(); const g1_input2 = input(); @@ -1608,7 +1642,7 @@ suite('EditorGroupModel', () => { group1.openEditor(g1_input2, { active: true, pinned: false }); group1.openEditor(g1_input3, { active: false, pinned: true }); - let group2 = createEditorGroupModel(); + let group2 = createEditorGroupModel(undefined, 0); const g2_input1 = input(); const g2_input2 = input(); @@ -1668,7 +1702,7 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group = createEditorGroupModel(); + let group = createEditorGroupModel(undefined, 0); const serializableInput1 = input(); const nonSerializableInput2 = input('3', true); @@ -1712,7 +1746,7 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group = createEditorGroupModel(); + let group = createEditorGroupModel(undefined, 0); const serializableInput1 = input(); const nonSerializableInput2 = input('3', true); @@ -1747,8 +1781,8 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group1 = createEditorGroupModel(); - let group2 = createEditorGroupModel(); + let group1 = createEditorGroupModel(undefined, 0); + let group2 = createEditorGroupModel(undefined, 0); const serializableInput1 = input(); const serializableInput2 = input(); @@ -1769,8 +1803,8 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Editor Dispose', function () { - const group1 = createEditorGroupModel(); - const group2 = createEditorGroupModel(); + const group1 = createEditorGroupModel(undefined, 0); + const group2 = createEditorGroupModel(undefined, 0); const group1Listener = groupListener(group1); const group2Listener = groupListener(group2); @@ -1800,7 +1834,7 @@ suite('EditorGroupModel', () => { }); test('Preview tab does not have a stable position (https://github.com/microsoft/vscode/issues/8245)', function () { - const group1 = createEditorGroupModel(); + const group1 = createEditorGroupModel(undefined, 0); const input1 = input(); const input2 = input(); @@ -1815,8 +1849,8 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Editor Emits Dirty and Label Changed', function () { - const group1 = createEditorGroupModel(); - const group2 = createEditorGroupModel(); + const group1 = createEditorGroupModel(undefined, 0); + const group2 = createEditorGroupModel(undefined, 0); const input1 = input(); const input2 = input(); @@ -1825,23 +1859,31 @@ suite('EditorGroupModel', () => { group2.openEditor(input2, { pinned: true, active: true }); let dirty1Counter = 0; - group1.onDidChangeEditorDirty(() => { - dirty1Counter++; + group1.onDidModelChange((e) => { + if (e.kind === GroupChangeKind.EDITOR_DIRTY) { + dirty1Counter++; + } }); let dirty2Counter = 0; - group2.onDidChangeEditorDirty(() => { - dirty2Counter++; + group2.onDidModelChange((e) => { + if (e.kind === GroupChangeKind.EDITOR_DIRTY) { + dirty2Counter++; + } }); let label1ChangeCounter = 0; - group1.onDidChangeEditorLabel(() => { - label1ChangeCounter++; + group1.onDidModelChange((e) => { + if (e.kind === GroupChangeKind.EDITOR_LABEL) { + label1ChangeCounter++; + } }); let label2ChangeCounter = 0; - group2.onDidChangeEditorLabel(() => { - label2ChangeCounter++; + group2.onDidModelChange((e) => { + if (e.kind === GroupChangeKind.EDITOR_LABEL) { + label2ChangeCounter++; + } }); (input1).setDirty(); @@ -1868,7 +1910,7 @@ suite('EditorGroupModel', () => { }); test('Sticky Editors', function () { - const group = createEditorGroupModel(); + const group = createEditorGroupModel(undefined, 0); const input1 = input(); const input2 = input(); @@ -2132,8 +2174,8 @@ suite('EditorGroupModel', () => { }); test('onDidMoveEditor Event', () => { - const group1 = createEditorGroupModel(); - const group2 = createEditorGroupModel(); + const group1 = createEditorGroupModel(undefined, 0); + const group2 = createEditorGroupModel(undefined, 0); const input1group1 = input(); const input2group1 = input(); @@ -2161,8 +2203,8 @@ suite('EditorGroupModel', () => { }); test('onDidOpeneditor Event', () => { - const group1 = createEditorGroupModel(); - const group2 = createEditorGroupModel(); + const group1 = createEditorGroupModel(undefined, 0); + const group2 = createEditorGroupModel(undefined, 0); const group1Events = groupListener(group1); const group2Events = groupListener(group2); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 759c958bfddd2..b93be0d9dcb35 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -51,7 +51,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/common/decorations'; import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IEditorReplacement, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, ISaveEditorsOptions, IRevertAllEditorsOptions, PreferredGroup, IEditorsChangeEvent } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; @@ -144,6 +144,7 @@ import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/te import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; import { DeserializedTerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorSerializer'; +import { IGroupChangeEvent } from 'vs/workbench/common/editor/editorGroupModel'; import { env } from 'vs/base/common/process'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { @@ -817,6 +818,7 @@ export class TestEditorGroupView implements IEditorGroupView { onWillDispose: Event = Event.None; onDidGroupChange: Event = Event.None; + onDidModelChange: Event = Event.None; onWillCloseEditor: Event = Event.None; onDidCloseEditor: Event = Event.None; onDidOpenEditorFail: Event = Event.None; From 726f1252b16b7bb9ba1733438f4b30173d127f05 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 6 Dec 2021 18:05:15 +0100 Subject: [PATCH 0316/2210] Small improvements to types --- src/vs/editor/browser/editorBrowser.ts | 71 +++++++++---------- src/vs/editor/common/model.ts | 18 ++--- src/vs/editor/contrib/suggest/suggestModel.ts | 2 +- src/vs/monaco.d.ts | 70 +++++++++--------- 4 files changed, 80 insertions(+), 81 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 1ea6a36412862..735df7655fc5a 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -6,7 +6,6 @@ import { Event } from 'vs/base/common/event'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { OverviewRulerPosition, ConfigurationChangedEvent, EditorLayoutInfo, IComputedEditorOptions, EditorOption, FindComputedEditorOptionValueById, IEditorOptions, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -403,177 +402,177 @@ export interface ICodeEditor extends editorCommon.IEditor { * An event emitted when the content of the current model has changed. * @event */ - onDidChangeModelContent: Event; + readonly onDidChangeModelContent: Event; /** * An event emitted when the language of the current model has changed. * @event */ - onDidChangeModelLanguage: Event; + readonly onDidChangeModelLanguage: Event; /** * An event emitted when the language configuration of the current model has changed. * @event */ - onDidChangeModelLanguageConfiguration: Event; + readonly onDidChangeModelLanguageConfiguration: Event; /** * An event emitted when the options of the current model has changed. * @event */ - onDidChangeModelOptions: Event; + readonly onDidChangeModelOptions: Event; /** * An event emitted when the configuration of the editor has changed. (e.g. `editor.updateOptions()`) * @event */ - onDidChangeConfiguration: Event; + readonly onDidChangeConfiguration: Event; /** * An event emitted when the cursor position has changed. * @event */ - onDidChangeCursorPosition: Event; + readonly onDidChangeCursorPosition: Event; /** * An event emitted when the cursor selection has changed. * @event */ - onDidChangeCursorSelection: Event; + readonly onDidChangeCursorSelection: Event; /** * An event emitted when the model of this editor has changed (e.g. `editor.setModel()`). * @event */ - onDidChangeModel: Event; + readonly onDidChangeModel: Event; /** * An event emitted when the decorations of the current model have changed. * @event */ - onDidChangeModelDecorations: Event; + readonly onDidChangeModelDecorations: Event; /** * An event emitted when the text inside this editor gained focus (i.e. cursor starts blinking). * @event */ - onDidFocusEditorText(listener: () => void): IDisposable; + readonly onDidFocusEditorText: Event; /** * An event emitted when the text inside this editor lost focus (i.e. cursor stops blinking). * @event */ - onDidBlurEditorText(listener: () => void): IDisposable; + readonly onDidBlurEditorText: Event; /** * An event emitted when the text inside this editor or an editor widget gained focus. * @event */ - onDidFocusEditorWidget(listener: () => void): IDisposable; + readonly onDidFocusEditorWidget: Event; /** * An event emitted when the text inside this editor or an editor widget lost focus. * @event */ - onDidBlurEditorWidget(listener: () => void): IDisposable; + readonly onDidBlurEditorWidget: Event; /** * An event emitted before interpreting typed characters (on the keyboard). * @event * @internal */ - onWillType(listener: (text: string) => void): IDisposable; + readonly onWillType: Event; /** * An event emitted after interpreting typed characters (on the keyboard). * @event * @internal */ - onDidType(listener: (text: string) => void): IDisposable; + readonly onDidType: Event; /** * An event emitted after composition has started. */ - onDidCompositionStart(listener: () => void): IDisposable; + readonly onDidCompositionStart: Event; /** * An event emitted after composition has ended. */ - onDidCompositionEnd(listener: () => void): IDisposable; + readonly onDidCompositionEnd: Event; /** * An event emitted when editing failed because the editor is read-only. * @event */ - onDidAttemptReadOnlyEdit(listener: () => void): IDisposable; + readonly onDidAttemptReadOnlyEdit: Event; /** * An event emitted when users paste text in the editor. * @event */ - onDidPaste: Event; + readonly onDidPaste: Event; /** * An event emitted on a "mouseup". * @event */ - onMouseUp: Event; + readonly onMouseUp: Event; /** * An event emitted on a "mousedown". * @event */ - onMouseDown: Event; + readonly onMouseDown: Event; /** * An event emitted on a "mousedrag". * @internal * @event */ - onMouseDrag: Event; + readonly onMouseDrag: Event; /** * An event emitted on a "mousedrop". * @internal * @event */ - onMouseDrop: Event; + readonly onMouseDrop: Event; /** * An event emitted on a "mousedropcanceled". * @internal * @event */ - onMouseDropCanceled(listener: () => void): IDisposable; + readonly onMouseDropCanceled: Event; /** * An event emitted on a "contextmenu". * @event */ - onContextMenu: Event; + readonly onContextMenu: Event; /** * An event emitted on a "mousemove". * @event */ - onMouseMove: Event; + readonly onMouseMove: Event; /** * An event emitted on a "mouseleave". * @event */ - onMouseLeave: Event; + readonly onMouseLeave: Event; /** * An event emitted on a "mousewheel" * @event * @internal */ - onMouseWheel: Event; + readonly onMouseWheel: Event; /** * An event emitted on a "keyup". * @event */ - onKeyUp: Event; + readonly onKeyUp: Event; /** * An event emitted on a "keydown". * @event */ - onKeyDown: Event; + readonly onKeyDown: Event; /** * An event emitted when the layout of the editor has changed. * @event */ - onDidLayoutChange: Event; + readonly onDidLayoutChange: Event; /** * An event emitted when the content width or content height in the editor has changed. * @event */ - onDidContentSizeChange: Event; + readonly onDidContentSizeChange: Event; /** * An event emitted when the scroll in the editor has changed. * @event */ - onDidScrollChange: Event; + readonly onDidScrollChange: Event; /** * An event emitted when hidden areas change in the editor (e.g. due to folding). * @event */ - onDidChangeHiddenAreas: Event; + readonly onDidChangeHiddenAreas: Event; /** * Saves current view state of the editor in a serializable object. @@ -998,7 +997,7 @@ export interface IDiffEditor extends editorCommon.IEditor { * An event emitted when the diff information computed by this diff editor has been updated. * @event */ - onDidUpdateDiff(listener: () => void): IDisposable; + readonly onDidUpdateDiff: Event; /** * Saves current view state of the editor in a serializable object. diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 3608d9046d2d8..605714dfd3356 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -1193,14 +1193,14 @@ export interface ITextModel { * @internal * @event */ - onDidChangeContentOrInjectedText(listener: (e: ModelRawContentChangedEvent | ModelInjectedTextChangedEvent) => void): IDisposable; + readonly onDidChangeContentOrInjectedText: Event; /** * @deprecated Please use `onDidChangeContent` instead. * An event emitted when the contents of the model have changed. * @internal * @event */ - onDidChangeRawContent(listener: (e: ModelRawContentChangedEvent) => void): IDisposable; + readonly onDidChangeRawContent: Event; /** * An event emitted when the contents of the model have changed. * @event @@ -1210,38 +1210,38 @@ export interface ITextModel { * An event emitted when decorations of the model have changed. * @event */ - onDidChangeDecorations(listener: (e: IModelDecorationsChangedEvent) => void): IDisposable; + readonly onDidChangeDecorations: Event; /** * An event emitted when the model options have changed. * @event */ - onDidChangeOptions(listener: (e: IModelOptionsChangedEvent) => void): IDisposable; + readonly onDidChangeOptions: Event; /** * An event emitted when the language associated with the model has changed. * @event */ - onDidChangeLanguage(listener: (e: IModelLanguageChangedEvent) => void): IDisposable; + readonly onDidChangeLanguage: Event; /** * An event emitted when the language configuration associated with the model has changed. * @event */ - onDidChangeLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) => void): IDisposable; + readonly onDidChangeLanguageConfiguration: Event; /** * An event emitted when the tokens associated with the model have changed. * @event * @internal */ - onDidChangeTokens(listener: (e: IModelTokensChangedEvent) => void): IDisposable; + readonly onDidChangeTokens: Event; /** * An event emitted when the model has been attached to the first editor or detached from the last editor. * @event */ - onDidChangeAttached(listener: () => void): IDisposable; + readonly onDidChangeAttached: Event; /** * An event emitted right before disposing the model. * @event */ - onWillDispose(listener: () => void): IDisposable; + readonly onWillDispose: Event; /** * Destroy this model. diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index c519ea09ba2ec..36ff64e40512a 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -294,7 +294,7 @@ export class SuggestModel implements IDisposable { }; this._triggerCharacterListener.add(this._editor.onDidType(checkTriggerCharacter)); - this._triggerCharacterListener.add(this._editor.onDidCompositionEnd(checkTriggerCharacter)); + this._triggerCharacterListener.add(this._editor.onDidCompositionEnd(() => checkTriggerCharacter())); } // --- trigger/retrigger/cancel suggest diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 180bbd2b31942..ebc6c1b904d9f 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2036,32 +2036,32 @@ declare namespace monaco.editor { * An event emitted when decorations of the model have changed. * @event */ - onDidChangeDecorations(listener: (e: IModelDecorationsChangedEvent) => void): IDisposable; + readonly onDidChangeDecorations: IEvent; /** * An event emitted when the model options have changed. * @event */ - onDidChangeOptions(listener: (e: IModelOptionsChangedEvent) => void): IDisposable; + readonly onDidChangeOptions: IEvent; /** * An event emitted when the language associated with the model has changed. * @event */ - onDidChangeLanguage(listener: (e: IModelLanguageChangedEvent) => void): IDisposable; + readonly onDidChangeLanguage: IEvent; /** * An event emitted when the language configuration associated with the model has changed. * @event */ - onDidChangeLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) => void): IDisposable; + readonly onDidChangeLanguageConfiguration: IEvent; /** * An event emitted when the model has been attached to the first editor or detached from the last editor. * @event */ - onDidChangeAttached(listener: () => void): IDisposable; + readonly onDidChangeAttached: IEvent; /** * An event emitted right before disposing the model. * @event */ - onWillDispose(listener: () => void): IDisposable; + readonly onWillDispose: IEvent; /** * Destroy this model. */ @@ -4767,140 +4767,140 @@ declare namespace monaco.editor { * An event emitted when the content of the current model has changed. * @event */ - onDidChangeModelContent: IEvent; + readonly onDidChangeModelContent: IEvent; /** * An event emitted when the language of the current model has changed. * @event */ - onDidChangeModelLanguage: IEvent; + readonly onDidChangeModelLanguage: IEvent; /** * An event emitted when the language configuration of the current model has changed. * @event */ - onDidChangeModelLanguageConfiguration: IEvent; + readonly onDidChangeModelLanguageConfiguration: IEvent; /** * An event emitted when the options of the current model has changed. * @event */ - onDidChangeModelOptions: IEvent; + readonly onDidChangeModelOptions: IEvent; /** * An event emitted when the configuration of the editor has changed. (e.g. `editor.updateOptions()`) * @event */ - onDidChangeConfiguration: IEvent; + readonly onDidChangeConfiguration: IEvent; /** * An event emitted when the cursor position has changed. * @event */ - onDidChangeCursorPosition: IEvent; + readonly onDidChangeCursorPosition: IEvent; /** * An event emitted when the cursor selection has changed. * @event */ - onDidChangeCursorSelection: IEvent; + readonly onDidChangeCursorSelection: IEvent; /** * An event emitted when the model of this editor has changed (e.g. `editor.setModel()`). * @event */ - onDidChangeModel: IEvent; + readonly onDidChangeModel: IEvent; /** * An event emitted when the decorations of the current model have changed. * @event */ - onDidChangeModelDecorations: IEvent; + readonly onDidChangeModelDecorations: IEvent; /** * An event emitted when the text inside this editor gained focus (i.e. cursor starts blinking). * @event */ - onDidFocusEditorText(listener: () => void): IDisposable; + readonly onDidFocusEditorText: IEvent; /** * An event emitted when the text inside this editor lost focus (i.e. cursor stops blinking). * @event */ - onDidBlurEditorText(listener: () => void): IDisposable; + readonly onDidBlurEditorText: IEvent; /** * An event emitted when the text inside this editor or an editor widget gained focus. * @event */ - onDidFocusEditorWidget(listener: () => void): IDisposable; + readonly onDidFocusEditorWidget: IEvent; /** * An event emitted when the text inside this editor or an editor widget lost focus. * @event */ - onDidBlurEditorWidget(listener: () => void): IDisposable; + readonly onDidBlurEditorWidget: IEvent; /** * An event emitted after composition has started. */ - onDidCompositionStart(listener: () => void): IDisposable; + readonly onDidCompositionStart: IEvent; /** * An event emitted after composition has ended. */ - onDidCompositionEnd(listener: () => void): IDisposable; + readonly onDidCompositionEnd: IEvent; /** * An event emitted when editing failed because the editor is read-only. * @event */ - onDidAttemptReadOnlyEdit(listener: () => void): IDisposable; + readonly onDidAttemptReadOnlyEdit: IEvent; /** * An event emitted when users paste text in the editor. * @event */ - onDidPaste: IEvent; + readonly onDidPaste: IEvent; /** * An event emitted on a "mouseup". * @event */ - onMouseUp: IEvent; + readonly onMouseUp: IEvent; /** * An event emitted on a "mousedown". * @event */ - onMouseDown: IEvent; + readonly onMouseDown: IEvent; /** * An event emitted on a "contextmenu". * @event */ - onContextMenu: IEvent; + readonly onContextMenu: IEvent; /** * An event emitted on a "mousemove". * @event */ - onMouseMove: IEvent; + readonly onMouseMove: IEvent; /** * An event emitted on a "mouseleave". * @event */ - onMouseLeave: IEvent; + readonly onMouseLeave: IEvent; /** * An event emitted on a "keyup". * @event */ - onKeyUp: IEvent; + readonly onKeyUp: IEvent; /** * An event emitted on a "keydown". * @event */ - onKeyDown: IEvent; + readonly onKeyDown: IEvent; /** * An event emitted when the layout of the editor has changed. * @event */ - onDidLayoutChange: IEvent; + readonly onDidLayoutChange: IEvent; /** * An event emitted when the content width or content height in the editor has changed. * @event */ - onDidContentSizeChange: IEvent; + readonly onDidContentSizeChange: IEvent; /** * An event emitted when the scroll in the editor has changed. * @event */ - onDidScrollChange: IEvent; + readonly onDidScrollChange: IEvent; /** * An event emitted when hidden areas change in the editor (e.g. due to folding). * @event */ - onDidChangeHiddenAreas: IEvent; + readonly onDidChangeHiddenAreas: IEvent; /** * Saves current view state of the editor in a serializable object. */ @@ -5149,7 +5149,7 @@ declare namespace monaco.editor { * An event emitted when the diff information computed by this diff editor has been updated. * @event */ - onDidUpdateDiff(listener: () => void): IDisposable; + readonly onDidUpdateDiff: IEvent; /** * Saves current view state of the editor in a serializable object. */ From 7663e88b076502e1a7adb5ee81cd7ba65b38a0bc Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 6 Dec 2021 19:18:11 +0100 Subject: [PATCH 0317/2210] Fix treeshaking problems --- src/vs/editor/browser/editorExtensions.ts | 19 ------------ .../contrib/anchorSelect/anchorSelect.ts | 30 ++++++++----------- .../bracketMatching/bracketMatching.ts | 18 +++++------ .../editor/contrib/contextmenu/contextmenu.ts | 12 +++----- .../editor/contrib/cursorUndo/cursorUndo.ts | 18 +++++------ 5 files changed, 31 insertions(+), 66 deletions(-) diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 435236bde0a3d..c621864a4fed7 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -351,25 +351,6 @@ export abstract class EditorAction extends EditorCommand { public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise; } -interface IEditorContributionCtorWithGet { - get(editor: ICodeEditor): T | null; -} - -export abstract class EditorControllerAction extends EditorAction { - - protected abstract controller: IEditorContributionCtorWithGet; - - run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { - const controller = this.controller.get(editor); - if (!controller) { - return; - } - return this.runControllerAction(accessor, editor, controller, args); - } - - abstract runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: T, args: any): void | Promise; -} - export type EditorActionImplementation = (accessor: ServicesAccessor, editor: ICodeEditor, args: any) => boolean | Promise; export class MultiEditorAction extends EditorAction { diff --git a/src/vs/editor/contrib/anchorSelect/anchorSelect.ts b/src/vs/editor/contrib/anchorSelect/anchorSelect.ts index 5d9f449cb520d..ceaefc19bb1ac 100644 --- a/src/vs/editor/contrib/anchorSelect/anchorSelect.ts +++ b/src/vs/editor/contrib/anchorSelect/anchorSelect.ts @@ -9,7 +9,7 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./anchorSelect'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorControllerAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -93,11 +93,7 @@ class SelectionAnchorController implements IEditorContribution { } } -abstract class SelectionAnchorAction extends EditorControllerAction { - controller = SelectionAnchorController; -} - -class SetSelectionAnchor extends SelectionAnchorAction { +class SetSelectionAnchor extends EditorAction { constructor() { super({ id: 'editor.action.setSelectionAnchor', @@ -112,12 +108,12 @@ class SetSelectionAnchor extends SelectionAnchorAction { }); } - async runControllerAction(_accessor: ServicesAccessor, editor: ICodeEditor, controller: SelectionAnchorController): Promise { - controller.setSelectionAnchor(); + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + SelectionAnchorController.get(editor)?.setSelectionAnchor(); } } -class GoToSelectionAnchor extends SelectionAnchorAction { +class GoToSelectionAnchor extends EditorAction { constructor() { super({ id: 'editor.action.goToSelectionAnchor', @@ -127,12 +123,12 @@ class GoToSelectionAnchor extends SelectionAnchorAction { }); } - async runControllerAction(_accessor: ServicesAccessor, editor: ICodeEditor, controller: SelectionAnchorController): Promise { - controller.goToSelectionAnchor(); + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + SelectionAnchorController.get(editor)?.goToSelectionAnchor(); } } -class SelectFromAnchorToCursor extends SelectionAnchorAction { +class SelectFromAnchorToCursor extends EditorAction { constructor() { super({ id: 'editor.action.selectFromAnchorToCursor', @@ -147,12 +143,12 @@ class SelectFromAnchorToCursor extends SelectionAnchorAction { }); } - async runControllerAction(_accessor: ServicesAccessor, editor: ICodeEditor, controller: SelectionAnchorController): Promise { - controller.selectFromAnchorToCursor(); + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + SelectionAnchorController.get(editor)?.selectFromAnchorToCursor(); } } -class CancelSelectionAnchor extends SelectionAnchorAction { +class CancelSelectionAnchor extends EditorAction { constructor() { super({ id: 'editor.action.cancelSelectionAnchor', @@ -167,8 +163,8 @@ class CancelSelectionAnchor extends SelectionAnchorAction { }); } - async runControllerAction(_accessor: ServicesAccessor, editor: ICodeEditor, controller: SelectionAnchorController): Promise { - controller.cancelSelectionAnchor(); + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + SelectionAnchorController.get(editor)?.cancelSelectionAnchor(); } } diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index c23d5790657b4..ed9a249a4b078 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -8,7 +8,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import 'vs/css!./bracketMatching'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorControllerAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -26,11 +26,7 @@ import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/ const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', { dark: '#A0A0A0', light: '#A0A0A0', hc: '#A0A0A0' }, nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.')); -abstract class BracketMatchingAction extends EditorControllerAction { - controller = BracketMatchingController; -} - -class JumpToBracketAction extends BracketMatchingAction { +class JumpToBracketAction extends EditorAction { constructor() { super({ id: 'editor.action.jumpToBracket', @@ -45,12 +41,12 @@ class JumpToBracketAction extends BracketMatchingAction { }); } - public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: BracketMatchingController): void { - controller.jumpToBracket(); + public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + BracketMatchingController.get(editor)?.jumpToBracket(); } } -class SelectToBracketAction extends BracketMatchingAction { +class SelectToBracketAction extends EditorAction { constructor() { super({ id: 'editor.action.selectToBracket', @@ -75,12 +71,12 @@ class SelectToBracketAction extends BracketMatchingAction { }); } - public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: BracketMatchingController, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { let selectBrackets = true; if (args && args.selectBrackets === false) { selectBrackets = false; } - controller.selectToBracket(selectBrackets); + BracketMatchingController.get(editor)?.selectToBracket(selectBrackets); } } diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index 0d8805ef5e23d..8c82b0b2f4050 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -14,7 +14,7 @@ import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isIOS } from 'vs/base/common/platform'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { EditorControllerAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -264,11 +264,7 @@ export class ContextMenuController implements IEditorContribution { } } -abstract class ContextMenuControllerAction extends EditorControllerAction { - controller = ContextMenuController; -} - -class ShowContextMenu extends ContextMenuControllerAction { +class ShowContextMenu extends EditorAction { constructor() { super({ @@ -284,8 +280,8 @@ class ShowContextMenu extends ContextMenuControllerAction { }); } - public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: ContextMenuController): void { - controller.showContextMenu(); + public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + ContextMenuController.get(editor)?.showContextMenu(); } } diff --git a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts index feaa363904a24..e340506126a51 100644 --- a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts +++ b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts @@ -6,7 +6,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorControllerAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -125,11 +125,7 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr } } -abstract class CursorUndoRedoControllerAction extends EditorControllerAction { - controller = CursorUndoRedoController; -} - -export class CursorUndo extends CursorUndoRedoControllerAction { +export class CursorUndo extends EditorAction { constructor() { super({ id: 'cursorUndo', @@ -144,12 +140,12 @@ export class CursorUndo extends CursorUndoRedoControllerAction { }); } - public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: CursorUndoRedoController, args: any): void { - controller.cursorUndo(); + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + CursorUndoRedoController.get(editor)?.cursorUndo(); } } -export class CursorRedo extends CursorUndoRedoControllerAction { +export class CursorRedo extends EditorAction { constructor() { super({ id: 'cursorRedo', @@ -159,8 +155,8 @@ export class CursorRedo extends CursorUndoRedoControllerAction { }); } - public runControllerAction(accessor: ServicesAccessor, editor: ICodeEditor, controller: CursorUndoRedoController, args: any): void { - controller.cursorRedo(); + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + CursorUndoRedoController.get(editor)?.cursorRedo(); } } From 59e6984ec4b0daf82d87e99da4c25032f2dc437b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 6 Dec 2021 19:31:02 +0100 Subject: [PATCH 0318/2210] fix https://github.com/microsoft/vscode/issues/138284 --- src/vs/base/common/map.ts | 4 +- src/vs/base/test/common/map.test.ts | 62 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index b45efcf3784c8..9432c915627c0 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -599,7 +599,7 @@ export class TernarySearchTree { stack[i][1] = node.rotateLeft(); } else { // right, left -> double rotate - node.right = stack[i + 1][1] = stack[i + 1][1].rotateRight(); + node.right = node.right!.rotateRight(); stack[i][1] = node.rotateLeft(); } @@ -610,7 +610,7 @@ export class TernarySearchTree { stack[i][1] = node.rotateRight(); } else { // left, right -> double rotate - node.left = stack[i + 1][1] = stack[i + 1][1].rotateLeft(); + node.left = node.left!.rotateLeft(); stack[i][1] = node.rotateRight(); } } diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 633749fba9623..d19e30d995390 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -747,6 +747,68 @@ suite('Map', () => { assertTstDfs(trie, ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); }); + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284', function () { + + const keys = [ + URI.parse('fake-fs:/C'), + URI.parse('fake-fs:/A'), + URI.parse('fake-fs:/D'), + URI.parse('fake-fs:/B'), + ]; + + const tst = TernarySearchTree.forUris(); + + for (let item of keys) { + tst.set(item, true); + } + + assert.ok(tst._isBalanced()); + tst.delete(keys[0]); + assert.ok(tst._isBalanced()); + }); + + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (simple)', function () { + + const keys = ['C', 'A', 'D', 'B',]; + const tst = TernarySearchTree.forStrings(); + for (let item of keys) { + tst.set(item, true); + } + assertTstDfs(tst, ['A', true], ['B', true], ['C', true], ['D', true]); + + tst.delete(keys[0]); + assertTstDfs(tst, ['A', true], ['B', true], ['D', true]); + + { + const tst = TernarySearchTree.forStrings(); + tst.set('C', true); + tst.set('A', true); + tst.set('B', true); + assertTstDfs(tst, ['A', true], ['B', true], ['C', true]); + } + + }); + + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (random)', function () { + for (let round = 10; round >= 0; round--) { + const keys: URI[] = []; + for (let i = 0; i < 100; i++) { + keys.push(URI.from({ scheme: 'fake-fs', path: Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) })); + } + const tst = TernarySearchTree.forUris(); + + for (let item of keys) { + tst.set(item, true); + assert.ok(tst._isBalanced()); + } + + for (let item of keys) { + tst.delete(item); + assert.ok(tst._isBalanced()); + } + } + }); + test('TernarySearchTree (PathSegments) - lookup', function () { const map = new TernarySearchTree(new PathIterator()); From 32c2b3d4ff2b490f33abbd75d7845e11346021a5 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 6 Dec 2021 12:40:14 -0600 Subject: [PATCH 0319/2210] fix #138224 (#138538) --- .../contrib/terminal/common/terminalConfiguration.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 5802b5897ebe0..ecbcfa5a639e4 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -442,7 +442,7 @@ const terminalConfiguration: IConfigurationNode = { default: true }, [TerminalSettingId.LocalEchoLatencyThreshold]: { - description: localize('terminal.integrated.localEchoLatencyThreshold', "Experimental: length of network delay, in milliseconds, where local edits will be echoed on the terminal without waiting for server acknowledgement. If '0', local echo will always be on, and if '-1' it will be disabled."), + description: localize('terminal.integrated.localEchoLatencyThreshold', "Length of network delay, in milliseconds, where local edits will be echoed on the terminal without waiting for server acknowledgement. If '0', local echo will always be on, and if '-1' it will be disabled."), type: 'integer', minimum: -1, default: 30, @@ -459,7 +459,7 @@ const terminalConfiguration: IConfigurationNode = { default: 'auto' }, [TerminalSettingId.LocalEchoExcludePrograms]: { - description: localize('terminal.integrated.localEchoExcludePrograms', "Experimental: local echo will be disabled when any of these program names are found in the terminal title."), + description: localize('terminal.integrated.localEchoExcludePrograms', "Local echo will be disabled when any of these program names are found in the terminal title."), type: 'array', items: { type: 'string', @@ -468,7 +468,7 @@ const terminalConfiguration: IConfigurationNode = { default: DEFAULT_LOCAL_ECHO_EXCLUDE, }, [TerminalSettingId.LocalEchoStyle]: { - description: localize('terminal.integrated.localEchoStyle', "Experimental: terminal style of locally echoed text; either a font style or an RGB color."), + description: localize('terminal.integrated.localEchoStyle', "Terminal style of locally echoed text; either a font style or an RGB color."), default: 'dim', oneOf: [ { From 13c0ed811e7e6bf2a961b2ab8b6d086fa7822fb4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Dec 2021 19:42:05 +0100 Subject: [PATCH 0320/2210] :up: playwright & set trace names (#138539) --- package.json | 2 +- test/automation/src/code.ts | 2 ++ test/automation/src/playwrightDriver.ts | 17 +++++++++++++---- .../src/areas/workbench/data-loss.test.ts | 2 +- test/smoke/src/utils.ts | 19 +++++++++++++++---- yarn.lock | 18 +++++++++--------- 6 files changed, 41 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index f74bfda24a162..8d5adcaa7083d 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,7 @@ "optimist": "0.3.5", "p-all": "^1.0.0", "path-browserify": "^1.0.1", - "playwright": "1.16.3", + "playwright": "1.17.1", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 45389defd0e2e..c59a03eda5fe3 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -27,6 +27,8 @@ export interface SpawnOptions { web?: boolean; headless?: boolean; browser?: 'chromium' | 'webkit' | 'firefox'; + suiteTitle?: string; + testTitle?: string; } let stopped = false; diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index a7cc9ee74e347..ac8807a963b7c 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -43,7 +43,8 @@ class PlaywrightDriver implements IDriver { private readonly server: ChildProcess, private readonly browser: playwright.Browser, private readonly context: playwright.BrowserContext, - readonly page: playwright.Page + private readonly page: playwright.Page, + private readonly suiteTitle: string | undefined ) { } @@ -61,7 +62,14 @@ class PlaywrightDriver implements IDriver { async exitApplication() { try { - await this.warnAfter(this.context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) }), 5000, 'Stopping playwright trace took >5seconds'); + let traceFileName: string; + if (this.suiteTitle) { + traceFileName = `playwright-trace-${traceCounter++}-${this.suiteTitle.replace(/\s+/g, '-')}.zip`; + } else { + traceFileName = `playwright-trace-${traceCounter++}.zip`; + } + + await this.warnAfter(this.context.tracing.stop({ path: join(logsPath, traceFileName) }), 5000, 'Stopping playwright trace took >5seconds'); } catch (error) { console.warn(`Failed to stop playwright tracing: ${error}`); } @@ -188,6 +196,7 @@ let port = 9000; export interface PlaywrightOptions { readonly browser?: 'chromium' | 'webkit' | 'firefox'; readonly headless?: boolean; + readonly suiteTitle?: string; } export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, options: PlaywrightOptions = {}): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver }> { @@ -203,7 +212,7 @@ export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_P client: { dispose: () => { /* there is no client to dispose for browser, teardown is triggered via exitApplication call */ } }, - driver: new PlaywrightDriver(serverProcess, browser, context, page) + driver: new PlaywrightDriver(serverProcess, browser, context, page, options.suiteTitle) }; } @@ -266,7 +275,7 @@ async function launchBrowser(options: PlaywrightOptions, endpoint: string, works const context = await browser.newContext(); try { - await context.tracing.start({ screenshots: true, snapshots: true }); + await context.tracing.start({ screenshots: true, snapshots: true, sources: true, title: options.suiteTitle }); } catch (error) { console.warn(`Failed to start playwright tracing.`); // do not fail the build when this fails } diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 0d7a46e99eee5..6ea0d9a5b3730 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -150,7 +150,7 @@ export function setup(opts: ParsedArgs) { insidersApp = undefined; }); - it.skip('verifies that "hot exit" works for dirty files (without delay)', async function () { // TODO@bpasero enable test once 1.63 shipped + it.skip('verifies that "hot exit" works for dirty files (without delay)', async function () { // TODO@bpasero enable test once 1.64 shipped return testHotExit.call(this, undefined); }); diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index 01f26878f9399..fa960486b96ed 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -21,7 +21,19 @@ export function itRepeat(n: number, description: string, callback: (this: Contex export function beforeSuite(args: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { before(async function () { - this.app = await startApp(args, this.defaultOptions, optionsTransform); + const testTitle = this.currentTest?.title; + const suiteTitle = this.currentTest?.parent?.title; + + this.app = await startApp(args, this.defaultOptions, async opts => { + opts.suiteTitle = suiteTitle; + opts.testTitle = testTitle; + + if (optionsTransform) { + opts = await optionsTransform(opts); + } + + return opts; + }); }); } @@ -37,9 +49,8 @@ export async function startApp(args: minimist.ParsedArgs, options: ApplicationOp await app.start(); - if (args.log) { - const title = this.currentTest!.fullTitle(); - app.logger.log('*** Test start:', title); + if (args.log && options.testTitle) { + app.logger.log('*** Test start:', options.testTitle); } return app; diff --git a/yarn.lock b/yarn.lock index 6729427e4bbea..3a8a014bc6b19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7690,10 +7690,10 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -playwright-core@=1.16.3: - version "1.16.3" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.16.3.tgz#f466be9acaffb698654adfb0a17a4906ba936895" - integrity sha512-16hF27IvQheJee+DbhC941AUZLjbJgfZFWi9YPS4LKEk/lKFhZI+9TiFD0sboYqb9eaEWvul47uR5xxTVbE4iw== +playwright-core@=1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.17.1.tgz#a16e0f89284a0ed8ae6d77e1c905c84b8a2ba022" + integrity sha512-C3c8RpPiC3qr15fRDN6dx6WnUkPLFmST37gms2aoHPDRvp7EaGDPMMZPpqIm/QWB5J40xDrQCD4YYHz2nBTojQ== dependencies: commander "^8.2.0" debug "^4.1.1" @@ -7712,12 +7712,12 @@ playwright-core@=1.16.3: yauzl "^2.10.0" yazl "^2.5.1" -playwright@1.16.3: - version "1.16.3" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.16.3.tgz#27a292d9fa54fbac923998d3af58cd2b691f5ebe" - integrity sha512-nfJx/OpIb/8OexL3rYGxNN687hGyaM3XNpfuMzoPlrekURItyuiHHsNhC9oQCx3JDmCn5O3EyyyFCnrZjH6MpA== +playwright@1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.17.1.tgz#a6d63302ee40f41283c4bf869de261c4743a787c" + integrity sha512-DisCkW9MblDJNS3rG61p8LiLA2WA7IY/4A4W7DX4BphWe/HuWjKmGQptuk4NVIh5UuSwXpW/jaH2+ZgjHs3GMA== dependencies: - playwright-core "=1.16.3" + playwright-core "=1.17.1" plist@^3.0.1: version "3.0.4" From 33e67129bc064c9b7b467a035f5e0b53a2be1601 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 6 Dec 2021 14:00:34 -0600 Subject: [PATCH 0321/2210] fix #138359 (#138532) --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 77f16013e6813..6650805fff3bd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1598,8 +1598,10 @@ export function registerTerminalActions() { async run(accessor: ServicesAccessor) { const themeService = accessor.get(IThemeService); const groupService = accessor.get(ITerminalGroupService); + const notificationService = accessor.get(INotificationService); const picks: ITerminalQuickPickItem[] = []; - if (groupService.instances.length === 1) { + if (groupService.instances.length <= 1) { + notificationService.warn(localize('workbench.action.terminal.join.insufficientTerminals', 'Insufficient terminals for the join action')); return; } const otherInstances = groupService.instances.filter(i => i.instanceId !== groupService.activeInstance?.instanceId); @@ -1625,6 +1627,7 @@ export function registerTerminalActions() { } } if (picks.length === 0) { + notificationService.warn(localize('workbench.action.terminal.join.onlySplits', 'All terminals are joined already')); return; } const result = await accessor.get(IQuickInputService).pick(picks, {}); From 2fd934ec24f6228a680b551881348356597ca422 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 6 Dec 2021 11:31:30 -0800 Subject: [PATCH 0322/2210] Use markdownDescription --- src/vs/workbench/contrib/search/browser/search.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index cdc625287a546..5390a2556d4cf 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -980,7 +980,7 @@ configurationRegistry.registerConfiguration({ 'search.seedOnFocus': { type: 'boolean', default: false, - description: nls.localize('search.seedOnFocus', "Update the search query to the editor's selected text when focusing the search view. This happens either on click or when triggering the `workbench.views.search.focus` command.") + markdownDescription: nls.localize('search.seedOnFocus', "Update the search query to the editor's selected text when focusing the search view. This happens either on click or when triggering the `workbench.views.search.focus` command.") }, 'search.searchOnTypeDebouncePeriod': { type: 'number', From 715754bc2013bf5ab6e7930ef52e0aa5ba33babc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 6 Dec 2021 12:02:14 -0800 Subject: [PATCH 0323/2210] Remove 10k limit on copying text search results Fix #138431 --- .../contrib/search/browser/searchActions.ts | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 0e986e12e0da4..37764e4645540 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -667,10 +667,9 @@ function matchToString(match: Match, indent = 0): string { } const lineDelimiter = isWindows ? '\r\n' : '\n'; -function fileMatchToString(fileMatch: FileMatch, maxMatches: number, labelService: ILabelService): { text: string, count: number } { +function fileMatchToString(fileMatch: FileMatch, labelService: ILabelService): { text: string, count: number } { const matchTextRows = fileMatch.matches() .sort(searchMatchComparer) - .slice(0, maxMatches) .map(match => matchToString(match, 2)); const uriString = labelService.getUriLabel(fileMatch.resource, { noPrefix: true }); return { @@ -679,17 +678,17 @@ function fileMatchToString(fileMatch: FileMatch, maxMatches: number, labelServic }; } -function folderMatchToString(folderMatch: FolderMatchWithResource | FolderMatch, maxMatches: number, labelService: ILabelService): { text: string, count: number } { +function folderMatchToString(folderMatch: FolderMatchWithResource | FolderMatch, labelService: ILabelService): { text: string, count: number } { const fileResults: string[] = []; let numMatches = 0; const matches = folderMatch.matches().sort(searchMatchComparer); - for (let i = 0; i < folderMatch.fileCount() && numMatches < maxMatches; i++) { - const fileResult = fileMatchToString(matches[i], maxMatches - numMatches, labelService); + matches.forEach(match => { + const fileResult = fileMatchToString(match, labelService); numMatches += fileResult.count; fileResults.push(fileResult.text); - } + }); return { text: fileResults.join(lineDelimiter + lineDelimiter), @@ -697,7 +696,6 @@ function folderMatchToString(folderMatch: FolderMatchWithResource | FolderMatch, }; } -const maxClipboardMatches = 1e4; export const copyMatchCommand: ICommandHandler = async (accessor, match: RenderableMatch | undefined) => { if (!match) { const selection = getSelectedRow(accessor); @@ -715,9 +713,9 @@ export const copyMatchCommand: ICommandHandler = async (accessor, match: Rendera if (match instanceof Match) { text = matchToString(match); } else if (match instanceof FileMatch) { - text = fileMatchToString(match, maxClipboardMatches, labelService).text; + text = fileMatchToString(match, labelService).text; } else if (match instanceof FolderMatch) { - text = folderMatchToString(match, maxClipboardMatches, labelService).text; + text = folderMatchToString(match, labelService).text; } if (text) { @@ -725,14 +723,12 @@ export const copyMatchCommand: ICommandHandler = async (accessor, match: Rendera } }; -function allFolderMatchesToString(folderMatches: Array, maxMatches: number, labelService: ILabelService): string { +function allFolderMatchesToString(folderMatches: Array, labelService: ILabelService): string { const folderResults: string[] = []; - let numMatches = 0; folderMatches = folderMatches.sort(searchMatchComparer); - for (let i = 0; i < folderMatches.length && numMatches < maxMatches; i++) { - const folderResult = folderMatchToString(folderMatches[i], maxMatches - numMatches, labelService); + for (let i = 0; i < folderMatches.length; i++) { + const folderResult = folderMatchToString(folderMatches[i], labelService); if (folderResult.count) { - numMatches += folderResult.count; folderResults.push(folderResult.text); } } @@ -755,7 +751,7 @@ export const copyAllCommand: ICommandHandler = async (accessor) => { if (searchView) { const root = searchView.searchResult; - const text = allFolderMatchesToString(root.folderMatches(), maxClipboardMatches, labelService); + const text = allFolderMatchesToString(root.folderMatches(), labelService); await clipboardService.writeText(text); } }; From 08fe3f22479cffaab17aba215a803def6c0c2977 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 6 Dec 2021 14:03:15 -0600 Subject: [PATCH 0324/2210] finalize terminal location API (#138530) --- .../workbench/api/common/extHost.api.impl.ts | 3 -- .../common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.d.ts | 54 +++++++++++++++++++ .../vscode.proposed.terminalLocation.d.ts | 45 ---------------- 4 files changed, 54 insertions(+), 49 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.terminalLocation.d.ts diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index ed882131258b1..5e30cb10cb0ba 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -656,9 +656,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { if (typeof nameOrOptions === 'object') { - if ('location' in nameOrOptions) { - checkProposedApiEnabled(extension, 'terminalLocation'); - } if ('pty' in nameOrOptions) { return extHostTerminalService.createExtensionTerminal(nameOrOptions); } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index d413ce4beaba1..2c734e5a0d20d 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -49,7 +49,6 @@ export const allApiProposals = Object.freeze({ taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', - terminalLocation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalLocation.d.ts', terminalNameChangeEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalNameChangeEvent.d.ts', testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts', testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 08d8ec29761ca..afdb41a05051c 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -5922,6 +5922,50 @@ declare module 'vscode' { dispose(): void; } + /** + * The location of the terminal. + */ + export enum TerminalLocation { + /** + * In the terminal view + */ + Panel = 1, + /** + * In the editor area + */ + Editor = 2, + } + + /** + * Assumes a {@link TerminalLocation} of editor and allows specifying a {@link ViewColumn} and + * {@link preserveFocus} property + */ + export interface TerminalEditorLocationOptions { + /** + * A view column in which the {@link Terminal terminal} should be shown in the editor area. + * Use {@link ViewColumn.Active active} to open in the active editor group, other values are + * adjusted to be `Min(column, columnCount + 1)`, the + * {@link ViewColumn.Active active}-column is not adjusted. Use + * {@linkcode ViewColumn.Beside} to open the editor to the side of the currently active one. + */ + viewColumn: ViewColumn; + /** + * An optional flag that when `true` will stop the {@link Terminal} from taking focus. + */ + preserveFocus?: boolean; + } + + /** + * Uses the parent {@link Terminal}'s location for the terminal + */ + export interface TerminalSplitLocationOptions { + /** + * The parent terminal to split this terminal beside. This works whether the parent terminal + * is in the panel or the editor area. + */ + parentTerminal: Terminal; + } + /** * Represents the state of a {@link Terminal}. */ @@ -9698,6 +9742,11 @@ declare module 'vscode' { * recommended for the best contrast and consistency across themes. */ color?: ThemeColor; + + /** + * The {@link TerminalLocation} or {@link TerminalEditorLocationOptions} or {@link TerminalSplitLocationOptions} for the terminal. + */ + location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; } /** @@ -9726,6 +9775,11 @@ declare module 'vscode' { * recommended for the best contrast and consistency across themes. */ color?: ThemeColor; + + /** + * The {@link TerminalLocation} or {@link TerminalEditorLocationOptions} or {@link TerminalSplitLocationOptions} for the terminal. + */ + location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; } /** diff --git a/src/vscode-dts/vscode.proposed.terminalLocation.d.ts b/src/vscode-dts/vscode.proposed.terminalLocation.d.ts deleted file mode 100644 index 9c2a10a3febe7..0000000000000 --- a/src/vscode-dts/vscode.proposed.terminalLocation.d.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/45407 - - export interface TerminalOptions { - location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; - } - - export interface ExtensionTerminalOptions { - location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; - } - - export enum TerminalLocation { - Panel = 1, - Editor = 2, - } - - export interface TerminalEditorLocationOptions { - /** - * A view column in which the {@link Terminal terminal} should be shown in the editor area. - * Use {@link ViewColumn.Active active} to open in the active editor group, other values are - * adjusted to be `Min(column, columnCount + 1)`, the - * {@link ViewColumn.Active active}-column is not adjusted. Use - * {@linkcode ViewColumn.Beside} to open the editor to the side of the currently active one. - */ - viewColumn: ViewColumn; - /** - * An optional flag that when `true` will stop the {@link Terminal} from taking focus. - */ - preserveFocus?: boolean; - } - - export interface TerminalSplitLocationOptions { - /** - * The parent terminal to split this terminal beside. This works whether the parent terminal - * is in the panel or the editor area. - */ - parentTerminal: Terminal; - } -} From efd055b8ba76aed8335ba18f63aa2ee6d04f8525 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 6 Dec 2021 12:36:22 -0800 Subject: [PATCH 0325/2210] Refresh markdown preview on all file changes (#138301) * Refresh markdown preview on all file changes * Don't watch `https`, `http`, and `data` uris --- extensions/markdown-language-features/src/features/preview.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 499f168afff5f..00545d2cef51a 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -120,6 +120,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { private imageInfo: { readonly id: string, readonly width: number, readonly height: number; }[] = []; private readonly _fileWatchersBySrc = new Map(); + private readonly _unwatchedImageSchemes = new Set(['https', 'http', 'data']); private readonly _onScrollEmitter = this._register(new vscode.EventEmitter()); public readonly onScroll = this._onScrollEmitter.event; @@ -427,7 +428,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { const root = vscode.Uri.joinPath(this._resource, '../'); for (const src of srcs) { const uri = urlToUri(src, root); - if (uri && uri.scheme === 'file' && !this._fileWatchersBySrc.has(src)) { + if (uri && !this._unwatchedImageSchemes.has(uri.scheme) && !this._fileWatchersBySrc.has(src)) { const watcher = vscode.workspace.createFileSystemWatcher(uri.fsPath); watcher.onDidChange(() => { this.refresh(true); From e40596a16c4359e9a1e99f4447e2f3cc32a35ee9 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 6 Dec 2021 12:46:51 -0800 Subject: [PATCH 0326/2210] Skip flaky updateImageSize suite Ref #138499 --- extensions/emmet/src/test/updateImageSize.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/emmet/src/test/updateImageSize.test.ts b/extensions/emmet/src/test/updateImageSize.test.ts index 606f26554b96d..9023285807b9d 100644 --- a/extensions/emmet/src/test/updateImageSize.test.ts +++ b/extensions/emmet/src/test/updateImageSize.test.ts @@ -9,7 +9,7 @@ import { Selection } from 'vscode'; import { withRandomFileEditor, closeAllEditors } from './testUtils'; import { updateImageSize } from '../updateImageSize'; -suite('Tests for Emmet actions on html tags', () => { +suite.skip('Tests for Emmet actions on html tags', () => { teardown(closeAllEditors); test('update image css with multiple cursors in css file', () => { From 9fca35e3886b3539fec28eda5154d118bbb1207c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Dec 2021 14:39:44 -0800 Subject: [PATCH 0327/2210] Pass locale to exthost for process revive date format Fixes #136855 --- src/vs/platform/terminal/common/terminal.ts | 2 +- src/vs/platform/terminal/node/ptyHostService.ts | 4 ++-- src/vs/platform/terminal/node/ptyService.ts | 4 ++-- .../contrib/terminal/browser/remoteTerminalBackend.ts | 2 +- .../contrib/terminal/common/remoteTerminalChannel.ts | 4 ++-- .../contrib/terminal/electron-sandbox/localTerminalBackend.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 026521405f7ff..e10c028a2b359 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -296,7 +296,7 @@ export interface IPtyService { * Revives a workspaces terminal processes, these can then be reconnected to using the normal * flow for restoring terminals after reloading. */ - reviveTerminalProcesses(state: string): Promise; + reviveTerminalProcesses(state: string, dateTimeFormatLocate: string): Promise; refreshProperty(id: number, property: T): Promise; updateProperty(id: number, property: T, value: IProcessPropertyMap[T]): Promise; diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 288462f847c5f..9081d9e6da4c8 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -299,8 +299,8 @@ export class PtyHostService extends Disposable implements IPtyService { return this._proxy.serializeTerminalState(ids); } - async reviveTerminalProcesses(state: string) { - return this._proxy.reviveTerminalProcesses(state); + async reviveTerminalProcesses(state: string, dateTimeFormatLocate: string) { + return this._proxy.reviveTerminalProcesses(state, dateTimeFormatLocate); } async refreshProperty(id: number, property: T): Promise { diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 1273db4f215ae..9d2e8d06e2323 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -122,7 +122,7 @@ export class PtyService extends Disposable implements IPtyService { return JSON.stringify(serialized); } - async reviveTerminalProcesses(state: string) { + async reviveTerminalProcesses(state: string, dateTimeFormatLocate: string) { const parsedUnknown = JSON.parse(state); if (!('version' in parsedUnknown) || !('state' in parsedUnknown) || !Array.isArray(parsedUnknown.state)) { this._logService.warn('Could not revive serialized processes, wrong format', parsedUnknown); @@ -138,7 +138,7 @@ export class PtyService extends Disposable implements IPtyService { const restoreMessage = localize({ key: 'terminal-session-restore', comment: ['date the snapshot was taken', 'time the snapshot was taken'] - }, "Session contents restored from {0} at {1}", new Date(state.timestamp).toLocaleDateString(), new Date(state.timestamp).toLocaleTimeString()); + }, "Session contents restored from {0} at {1}", new Date(state.timestamp).toLocaleDateString(dateTimeFormatLocate), new Date(state.timestamp).toLocaleTimeString(dateTimeFormatLocate)); const newId = await this.createProcess( { ...state.shellLaunchConfig, diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index 6ddd5af7bafa3..a54a059edde37 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -361,7 +361,7 @@ class RemoteTerminalBackend extends Disposable implements ITerminalBackend { const serializedState = this._storageService.get(TerminalStorageKeys.TerminalBufferState, StorageScope.WORKSPACE); if (serializedState) { try { - await this._remoteTerminalChannel.reviveTerminalProcesses(serializedState); + await this._remoteTerminalChannel.reviveTerminalProcesses(serializedState, Intl.DateTimeFormat().resolvedOptions().locale); this._storageService.remove(TerminalStorageKeys.TerminalBufferState, StorageScope.WORKSPACE); // If reviving processes, send the terminal layout info back to the pty host as it // will not have been persisted on application exit diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index 6bc36af3107c7..7db079e8e9224 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -294,8 +294,8 @@ export class RemoteTerminalChannelClient { return this._channel.call('$getTerminalLayoutInfo', args); } - reviveTerminalProcesses(state: string): Promise { - return this._channel.call('$reviveTerminalProcesses', [state]); + reviveTerminalProcesses(state: string, dateTimeFormatLocate: string): Promise { + return this._channel.call('$reviveTerminalProcesses', [state, dateTimeFormatLocate]); } serializeTerminalState(ids: number[]): Promise { diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index 2499563621d62..291f40d6c2088 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -246,7 +246,7 @@ class LocalTerminalBackend extends Disposable implements ITerminalBackend { const serializedState = this._storageService.get(TerminalStorageKeys.TerminalBufferState, StorageScope.WORKSPACE); if (serializedState) { try { - await this._localPtyService.reviveTerminalProcesses(serializedState); + await this._localPtyService.reviveTerminalProcesses(serializedState, Intl.DateTimeFormat().resolvedOptions().locale); this._storageService.remove(TerminalStorageKeys.TerminalBufferState, StorageScope.WORKSPACE); // If reviving processes, send the terminal layout info back to the pty host as it // will not have been persisted on application exit From 8bf3e4f34ea1fa82a3d4447c2bee6daa6a81923a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Dec 2021 14:55:10 -0800 Subject: [PATCH 0328/2210] Only restore terminal process name if source was api Fixes #138509 --- src/vs/platform/terminal/node/ptyService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 9d2e8d06e2323..1f2af0e28c5b4 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -145,7 +145,7 @@ export class PtyService extends Disposable implements IPtyService { cwd: state.processDetails.cwd, color: state.processDetails.color, icon: state.processDetails.icon, - name: state.processDetails.title, + name: state.processDetails.titleSource === TitleEventSource.Api ? state.processDetails.title : undefined, initialText: state.replayEvent.events[0].data + '\x1b[0m\n\n\r\x1b[1;48;5;252;38;5;234m ' + restoreMessage + ' \x1b[K\x1b[0m\n\r' }, state.processDetails.cwd, From a432d39da5ebaed9777fff38e1fc0cf376d353c8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Dec 2021 15:23:26 -0800 Subject: [PATCH 0329/2210] Strip relative path prefixes when checking for validated links A relative path prefix on a link is a strong signal that it's a complete file name, so use that to check for an exact relative to the workspace directory. Fixes #138508 --- .../terminal/browser/links/terminalLinkManager.ts | 14 +++++++++++--- .../links/terminalValidatedLocalLinkProvider.ts | 10 +++++++--- .../terminalValidatedLocalLinkProvider.test.ts | 12 +++++++++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index 9c42f1dfa4cda..927a381f4f5bf 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -78,7 +78,15 @@ export class TerminalLinkManager extends DisposableStore { wrappedTextLinkActivateCallback, this._wrapLinkHandler.bind(this), this._tooltipCallback.bind(this), - async (link, cb) => cb(await this._resolvePath(link))); + async (linkCandidates, cb) => { + for (const link of linkCandidates) { + const result = await this._resolvePath(link); + if (result) { + return cb(result); + } + } + return cb(undefined); + }); this._standardLinkProviders.push(validatedProvider); } @@ -323,7 +331,7 @@ export class TerminalLinkManager extends DisposableStore { return link; } - private async _resolvePath(link: string): Promise<{ uri: URI, isDirectory: boolean } | undefined> { + private async _resolvePath(link: string): Promise<{ uri: URI, link: string, isDirectory: boolean } | undefined> { if (!this._processManager) { throw new Error('Process manager is required'); } @@ -352,7 +360,7 @@ export class TerminalLinkManager extends DisposableStore { try { const stat = await this._fileService.resolve(uri); - return { uri, isDirectory: stat.isDirectory }; + return { uri, link, isDirectory: stat.isDirectory }; } catch (e) { // Does not exist diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts index 29e3388ef511a..443665c56d948 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts @@ -58,7 +58,7 @@ export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider private readonly _activateFileCallback: (event: MouseEvent | undefined, link: string) => void, private readonly _wrapLinkHandler: (handler: (event: MouseEvent | undefined, link: string) => void) => XtermLinkMatcherHandler, private readonly _tooltipCallback: (link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) => void, - private readonly _validationCallback: (link: string, callback: (result: { uri: URI, isDirectory: boolean } | undefined) => void) => void, + private readonly _validationCallback: (link: string[], callback: (result: { uri: URI, link: string, isDirectory: boolean } | undefined) => void) => void, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ICommandService private readonly _commandService: ICommandService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @@ -138,7 +138,11 @@ export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider }, startLine); const validatedLink = await new Promise(r => { - this._validationCallback(link, (result) => { + const linkCandidates = [link]; + if (link.match(/^(\.\.[\/\\])+/)) { + linkCandidates.push(link.replace(/^(\.\.[\/\\])+/, '')); + } + this._validationCallback(linkCandidates, (result) => { if (result) { const label = result.isDirectory ? (this._isDirectoryInsideWorkspace(result.uri) ? FOLDER_IN_WORKSPACE_LABEL : FOLDER_NOT_IN_WORKSPACE_LABEL) @@ -150,7 +154,7 @@ export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider this._activateFileCallback(event, text); } }); - r(this._instantiationService.createInstance(TerminalLink, this._xterm, bufferRange, link, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, true, label)); + r(this._instantiationService.createInstance(TerminalLink, this._xterm, bufferRange, result.link, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, true, label)); } else { r(undefined); } diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalValidatedLocalLinkProvider.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalValidatedLocalLinkProvider.test.ts index ea781ccc72f81..e393fc4f3416f 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalValidatedLocalLinkProvider.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalValidatedLocalLinkProvider.test.ts @@ -85,7 +85,17 @@ suite('Workbench - TerminalValidatedLocalLinkProvider', () => { async function assertLink(text: string, os: OperatingSystem, expected: { text: string, range: [number, number][] }[]) { const xterm = new Terminal(); - const provider = instantiationService.createInstance(TerminalValidatedLocalLinkProvider, xterm, os, () => { }, () => { }, () => { }, (_: string, cb: (result: { uri: URI, isDirectory: boolean } | undefined) => void) => { cb({ uri: URI.file('/'), isDirectory: false }); }); + const provider = instantiationService.createInstance( + TerminalValidatedLocalLinkProvider, + xterm, + os, + () => { }, + () => { }, + () => { }, + (linkCandidates: string, cb: (result: { uri: URI, link: string, isDirectory: boolean } | undefined) => void) => { + cb({ uri: URI.file('/'), link: linkCandidates[0], isDirectory: false }); + } + ); // Write the text and wait for the parser to finish await new Promise(r => xterm.write(text, r)); From e88394000fe7f7d46add98307c949ba37eabd737 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 6 Dec 2021 15:57:43 -0800 Subject: [PATCH 0330/2210] Fix markdown outline for cases without a space after `#` Fixes #138027 --- .../markdown-language-features/src/tableOfContentsProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/src/tableOfContentsProvider.ts b/extensions/markdown-language-features/src/tableOfContentsProvider.ts index fa414b145ee89..4f0c55d0a2c5b 100644 --- a/extensions/markdown-language-features/src/tableOfContentsProvider.ts +++ b/extensions/markdown-language-features/src/tableOfContentsProvider.ts @@ -117,6 +117,6 @@ export class TableOfContentsProvider { } private static getHeaderText(header: string): string { - return header.replace(/^\s*#+\s*(.*?)\s*#*$/, (_, word) => word.trim()); + return header.replace(/^\s*#+\s*(.*?)(\s+#+)?$/, (_, word) => word.trim()); } } From 925807b77db7dc215738f9df2ce4e4e1ede85217 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 6 Dec 2021 16:28:24 -0800 Subject: [PATCH 0331/2210] Fix find on web not handling next properly Fix #138275 Introduced by bbc10afedac1f9ea35d510211a5ec59296573cd5 The root problem bbc10afedac1f9ea35d510211a5ec59296573cd5 fixed is that `window.find` starts after the current selection. If your document consists of `abc abcd` for example, the following happens: 1. Type `a` in the find box 1. The first `a` is correctly highlighted 1. Type `b` to refine the search 1. The search no longer sees the first `a`, so we skip ahead to the second `a`, which is unexpected To fix this, I added code to collapse the selection before continuing the search. However we were also collapsing the selection when you hit the next button, which just resulted in the same word being re-selected again instead of skipping ahead to the next word. This fix avoids collapsing the selection when you are simply pressing next --- .../workbench/contrib/webview/browser/pre/main.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 16cc137a10b46..ed2e896233cc9 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -989,15 +989,21 @@ onDomReady(() => { assertIsDefined(target.contentDocument).execCommand(data); }); + /** @type {string | undefined} */ + let lastFindValue = undefined; + hostMessaging.onMessage('find', (_event, data) => { const target = getActiveFrame(); if (!target) { return; } - // Reset selection so we start search after current find result - const selection = target.contentWindow.getSelection(); - selection.collapse(selection.anchorNode); + if (!data.previous && lastFindValue !== data.value) { + // Reset selection so we start search at the head of the last search + const selection = target.contentWindow.getSelection(); + selection.collapse(selection.anchorNode); + } + lastFindValue = data.value; const didFind = (/** @type {any} */ (target.contentWindow)).find( data.value, @@ -1016,6 +1022,8 @@ onDomReady(() => { return; } + lastFindValue = undefined; + if (!data.clearSelection) { const selection = target.contentWindow.getSelection(); for (let i = 0; i < selection.rangeCount; i++) { From 0c2038d32d3d24e4d90dcb949bc92579955f2224 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 6 Dec 2021 16:30:32 -0800 Subject: [PATCH 0332/2210] Fixes #138247 --- src/vs/base/parts/quickinput/browser/media/quickInput.css | 2 +- src/vs/base/parts/quickinput/browser/quickInput.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 6548432adc583..c448eee320b92 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -129,7 +129,7 @@ .quick-input-message { margin-top: -1px; - padding: 5px 5px 2px 5px; + padding: 5px; overflow-wrap: break-word; } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 4a9a2db8cd2ce..fe76816a6e544 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -413,12 +413,12 @@ class QuickInput extends Disposable implements IQuickInput { this.ui.message.style.color = styles.foreground ? `${styles.foreground}` : ''; this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : ''; this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : ''; - this.ui.message.style.paddingBottom = '4px'; + this.ui.message.style.marginBottom = '-2px'; } else { this.ui.message.style.color = ''; this.ui.message.style.backgroundColor = ''; this.ui.message.style.border = ''; - this.ui.message.style.paddingBottom = ''; + this.ui.message.style.marginBottom = ''; } } From 974442023a6ebd555f8008dfdbfdb18a7dc9b33a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 6 Dec 2021 16:37:30 -0800 Subject: [PATCH 0333/2210] Better name for method --- .../workbench/contrib/webview/browser/webviewElement.ts | 2 +- .../contrib/webview/browser/webviewFindWidget.ts | 9 +++++---- .../contrib/webview/electron-sandbox/webviewElement.ts | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index a85f8dd96aa63..2b27a352c9d83 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -810,7 +810,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this._send('find', { value, previous }); } - public startFind(value: string) { + public updateFind(value: string) { if (!value || !this.element) { return; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts index e3778bb07d4da..fde2df743dd1a 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts @@ -14,13 +14,14 @@ export interface WebviewFindDelegate { readonly onDidStopFind: Event; readonly checkImeCompletionState: boolean; find(value: string, previous: boolean): void; - startFind(value: string): void; + updateFind(value: string): void; stopFind(keepSelection?: boolean): void; focus(): void; } export class WebviewFindWidget extends SimpleFindWidget { - protected _findWidgetFocused: IContextKey; + + protected readonly _findWidgetFocused: IContextKey; constructor( private readonly _delegate: WebviewFindDelegate, @@ -53,10 +54,10 @@ export class WebviewFindWidget extends SimpleFindWidget { this._delegate.focus(); } - public _onInputChanged(): boolean { + protected _onInputChanged(): boolean { const val = this.inputValue; if (val) { - this._delegate.startFind(val); + this._delegate.updateFind(val); } else { this._delegate.stopFind(false); } diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts index 55ee9927e7628..56f60d54d3a1f 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts @@ -107,7 +107,7 @@ export class ElectronWebviewElement extends WebviewElement { } if (!this._findStarted) { - this.startFind(value); + this.updateFind(value); } else { // continuing the find, so set findNext to false const options: FindInFrameOptions = { forward: !previous, findNext: false, matchCase: false }; @@ -115,7 +115,7 @@ export class ElectronWebviewElement extends WebviewElement { } } - public override startFind(value: string) { + public override updateFind(value: string) { if (!value || !this.element) { return; } From 4fb22d76cf8354cb8b111d82bdfadea38c4b8f34 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 6 Dec 2021 18:01:09 -0800 Subject: [PATCH 0334/2210] "Surround with" in TS/JS snippets (#138565) * Preserve selected text in snippets to provide "surround-with"-like functionality. * Add the same "surround with"-like functionality to JS snippets. --- .../snippets/javascript.code-snippets | 24 +++++++-------- .../snippets/typescript.code-snippets | 30 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/extensions/javascript/snippets/javascript.code-snippets b/extensions/javascript/snippets/javascript.code-snippets index a48a64c281322..ac5cd5549f156 100644 --- a/extensions/javascript/snippets/javascript.code-snippets +++ b/extensions/javascript/snippets/javascript.code-snippets @@ -17,7 +17,7 @@ "body": [ "for (let ${1:index} = 0; ${1:index} < ${2:array}.length; ${1:index}++) {", "\tconst ${3:element} = ${2:array}[${1:index}];", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "For Loop" @@ -26,7 +26,7 @@ "prefix": "foreach", "body": [ "${1:array}.forEach(${2:element} => {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "});" ], "description": "For-Each Loop" @@ -37,7 +37,7 @@ "for (const ${1:key} in ${2:object}) {", "\tif (Object.hasOwnProperty.call(${2:object}, ${1:key})) {", "\t\tconst ${3:element} = ${2:object}[${1:key}];", - "\t\t$0", + "\t\t$TM_SELECTED_TEXT$0", "\t}", "}" ], @@ -47,7 +47,7 @@ "prefix": "forof", "body": [ "for (const ${1:iterator} of ${2:object}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "For-Of Loop" @@ -56,7 +56,7 @@ "prefix": "function", "body": [ "function ${1:name}(${2:params}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "Function Statement" @@ -65,7 +65,7 @@ "prefix": "if", "body": [ "if (${1:condition}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "If Statement" @@ -74,7 +74,7 @@ "prefix": "ifelse", "body": [ "if (${1:condition}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "} else {", "\t", "}" @@ -106,7 +106,7 @@ "prefix": "while", "body": [ "while (${1:condition}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "While Statement" @@ -115,7 +115,7 @@ "prefix": "dowhile", "body": [ "do {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "} while (${1:condition});" ], "description": "Do-While Statement" @@ -124,7 +124,7 @@ "prefix": "trycatch", "body": [ "try {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "} catch (${1:error}) {", "\t", "}" @@ -135,7 +135,7 @@ "prefix": "settimeout", "body": [ "setTimeout(() => {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}, ${1:timeout});" ], "description": "Set Timeout Function" @@ -144,7 +144,7 @@ "prefix": "setinterval", "body": [ "setInterval(() => {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}, ${1:interval});" ], "description": "Set Interval Function" diff --git a/extensions/typescript-basics/snippets/typescript.code-snippets b/extensions/typescript-basics/snippets/typescript.code-snippets index 61155f40c417b..49ebefbac9a4b 100644 --- a/extensions/typescript-basics/snippets/typescript.code-snippets +++ b/extensions/typescript-basics/snippets/typescript.code-snippets @@ -133,7 +133,7 @@ "body": [ "for (let ${1:index} = 0; ${1:index} < ${2:array}.length; ${1:index}++) {", "\tconst ${3:element} = ${2:array}[${1:index}];", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "For Loop" @@ -142,7 +142,7 @@ "prefix": "foreach =>", "body": [ "${1:array}.forEach(${2:element} => {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "});" ], "description": "For-Each Loop using =>" @@ -153,7 +153,7 @@ "for (const ${1:key} in ${2:object}) {", "\tif (Object.prototype.hasOwnProperty.call(${2:object}, ${1:key})) {", "\t\tconst ${3:element} = ${2:object}[${1:key}];", - "\t\t$0", + "\t\t$TM_SELECTED_TEXT$0", "\t}", "}" ], @@ -163,7 +163,7 @@ "prefix": "forof", "body": [ "for (const ${1:iterator} of ${2:object}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "For-Of Loop" @@ -172,7 +172,7 @@ "prefix": "forawaitof", "body": [ "for await (const ${1:iterator} of ${2:object}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "For-Await-Of Loop" @@ -181,7 +181,7 @@ "prefix": "function", "body": [ "function ${1:name}(${2:params}:${3:type}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "Function Statement" @@ -190,7 +190,7 @@ "prefix": "if", "body": [ "if (${1:condition}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "If Statement" @@ -199,7 +199,7 @@ "prefix": "ifelse", "body": [ "if (${1:condition}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "} else {", "\t", "}" @@ -231,7 +231,7 @@ "prefix": "while", "body": [ "while (${1:condition}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "While Statement" @@ -240,7 +240,7 @@ "prefix": "dowhile", "body": [ "do {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "} while (${1:condition});" ], "description": "Do-While Statement" @@ -249,7 +249,7 @@ "prefix": "trycatch", "body": [ "try {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "} catch (${1:error}) {", "\t", "}" @@ -260,7 +260,7 @@ "prefix": "settimeout", "body": [ "setTimeout(() => {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}, ${1:timeout});" ], "description": "Set Timeout Function" @@ -283,7 +283,7 @@ "prefix": "newpromise", "body": [ "new Promise<${1:void}>((resolve, reject) => {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "})" ], "description": "Create a new Promise" @@ -292,7 +292,7 @@ "prefix": "async function", "body": [ "async function ${1:name}(${2:params}:${3:type}) {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "Async Function Statement" @@ -301,7 +301,7 @@ "prefix": "async arrow function", "body": [ "async (${1:params}:${2:type}) => {", - "\t$0", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "Async Function Expression" From 19210718356f6592bbec2a5ed36b233c642d0e92 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 6 Dec 2021 18:06:02 -0800 Subject: [PATCH 0335/2210] Added theme image support to the markdown preview (#137820) --- extensions/github/markdown.css | 4 ++++ extensions/github/package.json | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 extensions/github/markdown.css diff --git a/extensions/github/markdown.css b/extensions/github/markdown.css new file mode 100644 index 0000000000000..e6b118cb8d92f --- /dev/null +++ b/extensions/github/markdown.css @@ -0,0 +1,4 @@ +.vscode-dark img[src$=\#gh-light-mode-only], +.vscode-light img[src$=\#gh-dark-mode-only] { + display: none; +} diff --git a/extensions/github/package.json b/extensions/github/package.json index e21350c32869a..7c0013fd7a8a4 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -64,6 +64,9 @@ "contents": "%welcome.publishWorkspaceFolder%", "when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0" } + ], + "markdown.previewStyles": [ + "./markdown.css" ] }, "scripts": { From d07ee608294e3e4809abb73820eb9cb1246f057a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 6 Dec 2021 18:13:00 -0800 Subject: [PATCH 0336/2210] Update markdown grammar --- extensions/markdown-basics/cgmanifest.json | 2 +- .../syntaxes/markdown.tmLanguage.json | 74 ++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-basics/cgmanifest.json b/extensions/markdown-basics/cgmanifest.json index b30753f8d9922..c358375dd7591 100644 --- a/extensions/markdown-basics/cgmanifest.json +++ b/extensions/markdown-basics/cgmanifest.json @@ -33,7 +33,7 @@ "git": { "name": "microsoft/vscode-markdown-tm-grammar", "repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar", - "commitHash": "b0e9d49bdb9ff10332439db26550383b5d13aea7" + "commitHash": "402cd37bf494f7e04e2df15423ba91ce8a9b1301" } }, "license": "MIT", diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 987fa350aa93d..263501e2b9be4 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/b0e9d49bdb9ff10332439db26550383b5d13aea7", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/402cd37bf494f7e04e2df15423ba91ce8a9b1301", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -1781,6 +1781,72 @@ } ] }, + "fenced_code_block_latex": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(latex|tex)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.latex", + "patterns": [ + { + "include": "text.tex.latex" + } + ] + } + ] + }, + "fenced_code_block_bibtex": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bibtex)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.bibtex", + "patterns": [ + { + "include": "text.bibtex" + } + ] + } + ] + }, "fenced_code_block": { "patterns": [ { @@ -1939,6 +2005,12 @@ { "include": "#fenced_code_block_elixir" }, + { + "include": "#fenced_code_block_latex" + }, + { + "include": "#fenced_code_block_bibtex" + }, { "include": "#fenced_code_block_unknown" } From db4fbd3666e5573142e010ddfcb76897901441ca Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 7 Dec 2021 07:48:58 +0100 Subject: [PATCH 0337/2210] editors :lipstick: --- .../browser/parts/editor/editorGroupView.ts | 7 +- .../common/editor/editorGroupModel.ts | 8 +- .../services/editor/browser/editorService.ts | 13 ++- .../parts/editor/editorGroupModel.test.ts | 106 +++++++++--------- 4 files changed, 70 insertions(+), 64 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 1d9298b16e083..e9912707a476b 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -159,9 +159,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (from instanceof EditorGroupView) { this.model = this._register(from.model.clone()); } else if (isSerializedEditorGroupModel(from)) { - this.model = this._register(instantiationService.createInstance(EditorGroupModel, from, this._index)); + this.model = this._register(instantiationService.createInstance(EditorGroupModel, from)); } else { - this.model = this._register(instantiationService.createInstance(EditorGroupModel, undefined, this._index)); + this.model = this._register(instantiationService.createInstance(EditorGroupModel, undefined)); } //#region create() @@ -842,8 +842,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { notifyIndexChanged(newIndex: number): void { if (this._index !== newIndex) { this._index = newIndex; + this.model.setIndex(newIndex); + this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_INDEX }); - this.model.setIndex(this._index); } } diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 42d77d55f8616..e0315817e50d9 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -192,7 +192,6 @@ export class EditorGroupModel extends Disposable { constructor( labelOrSerializedGroup: ISerializedEditorGroupModel | undefined, - private index: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService ) { @@ -600,7 +599,10 @@ export class EditorGroupModel extends Disposable { } setIndex(index: number) { - this.index = index; + // We do not really keep the `index` in our model because + // it has no special meaning to us here. But for consistency + // we emit a `onDidModelChange` event so that components can + // react. this._onDidModelChange.fire({ kind: GroupChangeKind.GROUP_INDEX }); } @@ -895,7 +897,7 @@ export class EditorGroupModel extends Disposable { } clone(): EditorGroupModel { - const clone = this.instantiationService.createInstance(EditorGroupModel, undefined, this.index); + const clone = this.instantiationService.createInstance(EditorGroupModel, undefined); // Copy over group properties clone.editors = this.editors.slice(0); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index e02323045827e..9eba7dce57a42 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -147,19 +147,22 @@ export class EditorService extends Disposable implements EditorServiceImpl { private registerGroupListeners(group: IEditorGroupView): void { const groupDisposables = new DisposableStore(); - groupDisposables.add(group.onDidModelChange(e => this._onDidEditorsChange.fire([{ groupId: group.id, ...e }]))); + groupDisposables.add(group.onDidModelChange(e => { + this._onDidEditorsChange.fire([{ groupId: group.id, ...e }]); + })); - // Need to separatly listen to the group change for things like active editor changing - // as this doesn't always change the model (This could be a bug that needs more investigation) groupDisposables.add(group.onDidGroupChange(e => { + // TODO@lramos15 TODO@bpasero revisit the need for listening to + // this event specifically vs. using the `onDidModelChange` event + // https://github.com/microsoft/vscode/issues/138200 if (e.kind === GroupChangeKind.EDITOR_ACTIVE) { this.handleActiveEditorChange(group); this._onDidVisibleEditorsChange.fire(); } })); - groupDisposables.add(group.onDidCloseEditor(event => { - this._onDidCloseEditor.fire(event); + groupDisposables.add(group.onDidCloseEditor(e => { + this._onDidCloseEditor.fire(e); })); groupDisposables.add(group.onDidOpenEditorFail(editor => { diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts index 011bd5ec8408c..5cfaa0f4d8687 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts @@ -42,8 +42,8 @@ suite('EditorGroupModel', () => { return inst; } - function createEditorGroupModel(serialized: ISerializedEditorGroupModel | undefined, index: number): EditorGroupModel { - return inst().createInstance(EditorGroupModel, serialized, index); + function createEditorGroupModel(serialized?: ISerializedEditorGroupModel): EditorGroupModel { + return inst().createInstance(EditorGroupModel, serialized); } function closeAllEditors(group: EditorGroupModel): void { @@ -282,7 +282,7 @@ suite('EditorGroupModel', () => { }); test('Clone Group', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = input() as TestEditorInput; const input2 = input(); @@ -332,7 +332,7 @@ suite('EditorGroupModel', () => { }); test('isActive - untyped', () => { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input = new TestFileEditorInput('testInput', URI.file('fake')); const input2 = new TestFileEditorInput('testInput2', URI.file('fake2')); const untypedInput = { resource: URI.file('/fake'), options: { override: 'testInput' } }; @@ -349,7 +349,7 @@ suite('EditorGroupModel', () => { test('openEditor - prefers existing side by side editor if same', () => { const instantiationService = workbenchInstantiationService(undefined, disposables); - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = new TestFileEditorInput('testInput', URI.file('fake1')); const input2 = new TestFileEditorInput('testInput', URI.file('fake2')); @@ -377,7 +377,7 @@ suite('EditorGroupModel', () => { test('indexOf() - prefers direct matching editor over side by side matching one', () => { const instantiationService = workbenchInstantiationService(undefined, disposables); - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = new TestFileEditorInput('testInput', URI.file('fake1')); const sideBySideInput = instantiationService.createInstance(SideBySideEditorInput, undefined, undefined, input1, input1); @@ -395,7 +395,7 @@ suite('EditorGroupModel', () => { }); test('contains() - untyped', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const instantiationService = workbenchInstantiationService(undefined, disposables); const input1 = input('input1', false, URI.file('/input1')); @@ -516,7 +516,7 @@ suite('EditorGroupModel', () => { }); test('contains()', () => { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const instantiationService = workbenchInstantiationService(undefined, disposables); const input1 = input(); @@ -629,7 +629,7 @@ suite('EditorGroupModel', () => { test('group serialization', function () { inst().invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -641,7 +641,7 @@ suite('EditorGroupModel', () => { group.openEditor(input2, { pinned: true, active: true }); group.openEditor(input3, { pinned: false, active: true }); - let deserialized = createEditorGroupModel(group.serialize(), 0); + let deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 3); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 3); @@ -654,7 +654,7 @@ suite('EditorGroupModel', () => { // Case 2: inputs cannot be serialized TestEditorInputSerializer.disableSerialize = true; - deserialized = createEditorGroupModel(group.serialize(), 0); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 0); @@ -664,7 +664,7 @@ suite('EditorGroupModel', () => { TestEditorInputSerializer.disableSerialize = false; TestEditorInputSerializer.disableDeserialize = true; - deserialized = createEditorGroupModel(group.serialize(), 0); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 0); @@ -673,7 +673,7 @@ suite('EditorGroupModel', () => { test('group serialization (sticky editor)', function () { inst().invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -688,7 +688,7 @@ suite('EditorGroupModel', () => { group.stick(input2); assert.ok(group.isSticky(input2)); - let deserialized = createEditorGroupModel(group.serialize(), 0); + let deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 3); @@ -707,7 +707,7 @@ suite('EditorGroupModel', () => { // Case 2: inputs cannot be serialized TestEditorInputSerializer.disableSerialize = true; - deserialized = createEditorGroupModel(group.serialize(), 0); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.stickyCount, 0); @@ -718,7 +718,7 @@ suite('EditorGroupModel', () => { TestEditorInputSerializer.disableSerialize = false; TestEditorInputSerializer.disableDeserialize = true; - deserialized = createEditorGroupModel(group.serialize(), 0); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.stickyCount, 0); @@ -727,7 +727,7 @@ suite('EditorGroupModel', () => { }); test('group serialization (locked group)', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const events = groupListener(group); @@ -745,23 +745,23 @@ suite('EditorGroupModel', () => { }); test('locked group', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); group.lock(true); - let deserialized = createEditorGroupModel(group.serialize(), 0); + let deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.isLocked, true); group.lock(false); - deserialized = createEditorGroupModel(group.serialize(), 0); + deserialized = createEditorGroupModel(group.serialize()); assert.strictEqual(group.id, deserialized.id); assert.strictEqual(deserialized.count, 0); assert.strictEqual(deserialized.isLocked, false); }); test('One Editor', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const events = groupListener(group); assert.strictEqual(group.count, 0); @@ -878,7 +878,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Pinned and Active', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input('1'); @@ -952,7 +952,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Preview editor moves to the side of the active one', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -1005,7 +1005,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Pinned and Not Active', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -1037,7 +1037,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Preview gets overwritten', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -1070,7 +1070,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - set active', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -1105,7 +1105,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - pin and unpin', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -1154,7 +1154,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - closing picks next from MRU list', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -1261,7 +1261,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - move editor', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const events = groupListener(group); const input1 = input(); @@ -1332,8 +1332,8 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - move editor across groups', function () { - const group1 = createEditorGroupModel(undefined, 0); - const group2 = createEditorGroupModel(undefined, 0); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const g1_input1 = input(); const g1_input2 = input(); @@ -1354,8 +1354,8 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - move editor across groups (input already exists in group 1)', function () { - const group1 = createEditorGroupModel(undefined, 0); - const group2 = createEditorGroupModel(undefined, 0); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const g1_input1 = input(); const g1_input2 = input(); @@ -1378,7 +1378,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Pinned & Non Active', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = input(); group.openEditor(input1); @@ -1409,7 +1409,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Close Others, Close Left, Close Right', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -1464,7 +1464,7 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - real user example', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); // [] -> /index.html/ const indexHtml = input('index.html'); @@ -1598,7 +1598,7 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group = createEditorGroupModel(undefined, 0); + let group = createEditorGroupModel(); const input1 = input(); group.openEditor(input1); @@ -1632,7 +1632,7 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group1 = createEditorGroupModel(undefined, 0); + let group1 = createEditorGroupModel(); const g1_input1 = input(); const g1_input2 = input(); @@ -1642,7 +1642,7 @@ suite('EditorGroupModel', () => { group1.openEditor(g1_input2, { active: true, pinned: false }); group1.openEditor(g1_input3, { active: false, pinned: true }); - let group2 = createEditorGroupModel(undefined, 0); + let group2 = createEditorGroupModel(); const g2_input1 = input(); const g2_input2 = input(); @@ -1702,7 +1702,7 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group = createEditorGroupModel(undefined, 0); + let group = createEditorGroupModel(); const serializableInput1 = input(); const nonSerializableInput2 = input('3', true); @@ -1746,7 +1746,7 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group = createEditorGroupModel(undefined, 0); + let group = createEditorGroupModel(); const serializableInput1 = input(); const nonSerializableInput2 = input('3', true); @@ -1781,8 +1781,8 @@ suite('EditorGroupModel', () => { inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); - let group1 = createEditorGroupModel(undefined, 0); - let group2 = createEditorGroupModel(undefined, 0); + let group1 = createEditorGroupModel(); + let group2 = createEditorGroupModel(); const serializableInput1 = input(); const serializableInput2 = input(); @@ -1803,8 +1803,8 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Editor Dispose', function () { - const group1 = createEditorGroupModel(undefined, 0); - const group2 = createEditorGroupModel(undefined, 0); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const group1Listener = groupListener(group1); const group2Listener = groupListener(group2); @@ -1834,7 +1834,7 @@ suite('EditorGroupModel', () => { }); test('Preview tab does not have a stable position (https://github.com/microsoft/vscode/issues/8245)', function () { - const group1 = createEditorGroupModel(undefined, 0); + const group1 = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -1849,8 +1849,8 @@ suite('EditorGroupModel', () => { }); test('Multiple Editors - Editor Emits Dirty and Label Changed', function () { - const group1 = createEditorGroupModel(undefined, 0); - const group2 = createEditorGroupModel(undefined, 0); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -1910,7 +1910,7 @@ suite('EditorGroupModel', () => { }); test('Sticky Editors', function () { - const group = createEditorGroupModel(undefined, 0); + const group = createEditorGroupModel(); const input1 = input(); const input2 = input(); @@ -2174,8 +2174,8 @@ suite('EditorGroupModel', () => { }); test('onDidMoveEditor Event', () => { - const group1 = createEditorGroupModel(undefined, 0); - const group2 = createEditorGroupModel(undefined, 0); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const input1group1 = input(); const input2group1 = input(); @@ -2203,8 +2203,8 @@ suite('EditorGroupModel', () => { }); test('onDidOpeneditor Event', () => { - const group1 = createEditorGroupModel(undefined, 0); - const group2 = createEditorGroupModel(undefined, 0); + const group1 = createEditorGroupModel(); + const group2 = createEditorGroupModel(); const group1Events = groupListener(group1); const group2Events = groupListener(group2); From 470cee7f66e0284846ae902308cc35ef69ab7d59 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Dec 2021 07:56:25 +0100 Subject: [PATCH 0338/2210] update API notebook milestone --- .vscode/notebooks/api.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index cf580ac17be3e..14d330b52cfb5 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"November 2021\"" + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"December 2021\"" }, { "kind": 1, From 5f63d720d583f476a4c3a807774a674b622fcd2e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 7 Dec 2021 08:38:58 +0100 Subject: [PATCH 0339/2210] backups - suspend/resume onWillShutdown --- .../common/workingCopyBackupTracker.ts | 48 +++++++++++++++---- .../workingCopyBackupTracker.ts | 29 +++++++---- .../workingCopyBackupTracker.test.ts | 16 ++++++- 3 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts index 2766c23d3c7e6..edee053cc6187 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts @@ -64,6 +64,8 @@ export abstract class WorkingCopyBackupTracker extends Disposable { this._register(this.workingCopyEditorService.onDidRegisterHandler(handler => this.restoreBackups(handler))); } + protected abstract onBeforeShutdown(reason: ShutdownReason): boolean | Promise; + private onWillShutdown(): void { // Here we know that we will shutdown. Any backup operation that is @@ -80,14 +82,6 @@ export abstract class WorkingCopyBackupTracker extends Disposable { //#region Backup Creator - // A map from working copy to a version ID we compute on each content - // change. This version ID allows to e.g. ask if a backup for a specific - // content has been made before closing. - private readonly mapWorkingCopyToContentVersion = new Map(); - - // A map of scheduled pending backup operations for working copies - protected readonly pendingBackupOperations = new Map(); - // Delay creation of backups when content changes to avoid too much // load on the backup service when the user is typing into the editor // Since we always schedule a backup, even when auto save is on, we @@ -102,7 +96,22 @@ export abstract class WorkingCopyBackupTracker extends Disposable { [AutoSaveMode.AFTER_LONG_DELAY]: 1000 }; + // A map from working copy to a version ID we compute on each content + // change. This version ID allows to e.g. ask if a backup for a specific + // content has been made before closing. + private readonly mapWorkingCopyToContentVersion = new Map(); + + // A map of scheduled pending backup operations for working copies + protected readonly pendingBackupOperations = new Map(); + + private suspended = false; + private onDidRegister(workingCopy: IWorkingCopy): void { + if (this.suspended) { + this.logService.warn(`[backup tracker] suspended, ignoring register event`, workingCopy.resource.toString(true), workingCopy.typeId); + return; + } + if (workingCopy.isDirty()) { this.scheduleBackup(workingCopy); } @@ -113,11 +122,22 @@ export abstract class WorkingCopyBackupTracker extends Disposable { // Remove from content version map this.mapWorkingCopyToContentVersion.delete(workingCopy); + // Check suspended + if (this.suspended) { + this.logService.warn(`[backup tracker] suspended, ignoring unregister event`, workingCopy.resource.toString(true), workingCopy.typeId); + return; + } + // Discard backup this.discardBackup(workingCopy); } private onDidChangeDirty(workingCopy: IWorkingCopy): void { + if (this.suspended) { + this.logService.warn(`[backup tracker] suspended, ignoring dirty change event`, workingCopy.resource.toString(true), workingCopy.typeId); + return; + } + if (workingCopy.isDirty()) { this.scheduleBackup(workingCopy); } else { @@ -131,6 +151,12 @@ export abstract class WorkingCopyBackupTracker extends Disposable { const contentVersionId = this.getContentVersion(workingCopy); this.mapWorkingCopyToContentVersion.set(workingCopy, contentVersionId + 1); + // Check suspended + if (this.suspended) { + this.logService.warn(`[backup tracker] suspended, ignoring content change event`, workingCopy.resource.toString(true), workingCopy.typeId); + return; + } + // Schedule backup if dirty if (workingCopy.isDirty()) { // this listener will make sure that the backup is @@ -248,7 +274,11 @@ export abstract class WorkingCopyBackupTracker extends Disposable { this.pendingBackupOperations.clear(); } - protected abstract onBeforeShutdown(reason: ShutdownReason): boolean | Promise; + protected suspendBackupOperations(): { resume: () => void } { + this.suspended = true; + + return { resume: () => this.suspended = false }; + } //#endregion diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index 911ae767e91b8..2ea14315943bf 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -48,7 +48,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp super(workingCopyBackupService, workingCopyService, logService, lifecycleService, filesConfigurationService, workingCopyEditorService, editorService, editorGroupService); } - protected onBeforeShutdown(reason: ShutdownReason): Promise { + protected async onBeforeShutdown(reason: ShutdownReason): Promise { // Important: we are about to shutdown and handle dirty working copies // and backups. We do not want any pending backup ops to interfer with @@ -58,14 +58,27 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp // (https://github.com/microsoft/vscode/issues/138055) this.cancelBackupOperations(); - // Dirty working copies need treatment on shutdown - const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; - if (dirtyWorkingCopies.length) { - return this.onBeforeShutdownWithDirty(reason, dirtyWorkingCopies); - } + // For the duration of the shutdown handling, suspend backup operations + // and only resume after we have handled backups. Similar to above, we + // do not want to trigger backup tracking during our shutdown handling + // but we must resume, in case of a veto afterwards. + const { resume } = this.suspendBackupOperations(); + + try { - // No dirty working copies - return this.onBeforeShutdownWithoutDirty(); + // Dirty working copies need treatment on shutdown + const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; + if (dirtyWorkingCopies.length) { + return await this.onBeforeShutdownWithDirty(reason, dirtyWorkingCopies); + } + + // No dirty working copies + else { + return await this.onBeforeShutdownWithoutDirty(); + } + } finally { + resume(); + } } protected async onBeforeShutdownWithDirty(reason: ShutdownReason, dirtyWorkingCopies: readonly IWorkingCopy[]): Promise { diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index 840d98f2c0926..d61b2baf490c4 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -43,6 +43,7 @@ import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/com import { TestContextService, TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { timeout } from 'vs/base/common/async'; flakySuite('WorkingCopyBackupTracker (native)', function () { @@ -360,7 +361,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { await cleanup(); }); - test('onWillShutdown - pending backup operations canceled', async function () { + test('onWillShutdown - pending backup operations canceled and new ones suspended', async function () { const { accessor, tracker, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -374,10 +375,23 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { assert.strictEqual(tracker.pendingBackupOperationCount, 1); const event = new TestBeforeShutdownEvent(); + const finalVeto = timeout(1).then(() => false); + event.finalVeto(() => finalVeto); accessor.lifecycleService.fireBeforeShutdown(event); assert.strictEqual(tracker.pendingBackupOperationCount, 0); + model?.textEditorModel?.setValue('bar'); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); + assert.strictEqual(tracker.pendingBackupOperationCount, 0); + + await finalVeto; + + // Ops are resumed after handling! + model?.textEditorModel?.setValue('foo'); + assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); + assert.strictEqual(tracker.pendingBackupOperationCount, 1); + await cleanup(); }); From 5c31535ea8e984e249351f9fa56fb4990040cd61 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Dec 2021 09:22:48 +0100 Subject: [PATCH 0340/2210] add more API rpc tests, https://github.com/microsoft/vscode/issues/115679 --- .../src/singlefolder-tests/rpc.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts index 6f8ddc7a21264..873c6a697b749 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/rpc.test.ts @@ -105,4 +105,28 @@ suite('vscode', function () { dispo.push(ctrl); assertNoRpcFromEntry([ctrl, 'NotebookController']); }); + + test('no rpc, createTerminal(...)', function () { + const ctrl = vscode.window.createTerminal({ name: 'termi' }); + dispo.push(ctrl); + assertNoRpcFromEntry([ctrl, 'Terminal']); + }); + + test('no rpc, createFileSystemWatcher(...)', function () { + const item = vscode.workspace.createFileSystemWatcher('**/*.ts'); + dispo.push(item); + assertNoRpcFromEntry([item, 'FileSystemWatcher']); + }); + + test('no rpc, createTestController(...)', function () { + const item = vscode.tests.createTestController('iii', 'lll'); + dispo.push(item); + assertNoRpcFromEntry([item, 'TestController']); + }); + + test('no rpc, createLanguageStatusItem(...)', function () { + const item = vscode.languages.createLanguageStatusItem('i', '*'); + dispo.push(item); + assertNoRpcFromEntry([item, 'LanguageStatusItem']); + }); }); From 6b8ae546204fbb4cd04f3f34c3acfdbd352adece Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 7 Dec 2021 10:35:29 +0100 Subject: [PATCH 0341/2210] backups - fix tests --- .../test/electron-browser/workingCopyBackupTracker.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index d61b2baf490c4..d8fd64b9efdbc 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -388,6 +388,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { await finalVeto; // Ops are resumed after handling! + await timeout(1); // needed because resume is called on next loop model?.textEditorModel?.setValue('foo'); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); assert.strictEqual(tracker.pendingBackupOperationCount, 1); From f8d37f37562d4dadf44ccc61e309126fc9b66d09 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 7 Dec 2021 11:01:18 +0100 Subject: [PATCH 0342/2210] backups - avoid timeout in test --- .../workingCopyBackupTracker.test.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index d8fd64b9efdbc..2f3e40da472e0 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -43,7 +43,7 @@ import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/com import { TestContextService, TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopy'; -import { timeout } from 'vs/base/common/async'; +import { Event, Emitter } from 'vs/base/common/event'; flakySuite('WorkingCopyBackupTracker (native)', function () { @@ -85,6 +85,21 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { disposable.dispose(); } } + + private readonly _onDidResume = this._register(new Emitter()); + readonly onDidResume = this._onDidResume.event; + + protected override suspendBackupOperations(): { resume: () => void; } { + const { resume } = super.suspendBackupOperations(); + + return { + resume: () => { + resume(); + + this._onDidResume.fire(); + } + }; + } } let testDir: string; @@ -361,7 +376,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { await cleanup(); }); - test('onWillShutdown - pending backup operations canceled and new ones suspended', async function () { + test('onWillShutdown - pending backup operations canceled', async function () { const { accessor, tracker, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -375,22 +390,21 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { assert.strictEqual(tracker.pendingBackupOperationCount, 1); const event = new TestBeforeShutdownEvent(); - const finalVeto = timeout(1).then(() => false); - event.finalVeto(() => finalVeto); accessor.lifecycleService.fireBeforeShutdown(event); assert.strictEqual(tracker.pendingBackupOperationCount, 0); + // Ops are suspended during shutdown! model?.textEditorModel?.setValue('bar'); assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); assert.strictEqual(tracker.pendingBackupOperationCount, 0); - await finalVeto; + const onResume = Event.toPromise(tracker.onDidResume); + await event.value; - // Ops are resumed after handling! - await timeout(1); // needed because resume is called on next loop + // Ops are resumed after shutdown! model?.textEditorModel?.setValue('foo'); - assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); + await onResume; assert.strictEqual(tracker.pendingBackupOperationCount, 1); await cleanup(); From b2de74cb1ff772be0af36fdc176e079431e89cb5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 7 Dec 2021 11:22:32 +0100 Subject: [PATCH 0343/2210] Implements dynamic dependency check (to address #138348). --- src/vs/editor/common/config/commonEditorConfig.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 45b3bd7bcb52a..8e1801c507e99 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -64,6 +64,9 @@ const hasOwnProperty = Object.hasOwnProperty; export class ComputedEditorOptions implements IComputedEditorOptions { private readonly _values: any[] = []; public _read(id: EditorOption): T { + if (id >= this._values.length) { + throw new Error('Cannot read uninitialized value'); + } return this._values[id]; } public get(id: T): FindComputedEditorOptionValueById { From 3d13b9aeb482c09330c8872b3bc79de5750d265c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Dec 2021 11:27:33 +0100 Subject: [PATCH 0344/2210] expose CancellationToken on onWill-file events --- src/vs/base/common/event.ts | 8 ++++++-- .../api/common/extHostFileSystemEventService.ts | 4 ++-- src/vscode-dts/vscode.d.ts | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 605eb2d2b4a61..bd9d04eb781f0 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -600,14 +600,17 @@ export class Emitter { export interface IWaitUntil { + token: CancellationToken; waitUntil(thenable: Promise): void; } +export type IWaitUntilData = Omit, 'token'>; + export class AsyncEmitter extends Emitter { - private _asyncDeliveryQueue?: LinkedList<[Listener, Omit]>; + private _asyncDeliveryQueue?: LinkedList<[Listener, IWaitUntilData]>; - async fireAsync(data: Omit, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise { + async fireAsync(data: IWaitUntilData, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise { if (!this._listeners) { return; } @@ -627,6 +630,7 @@ export class AsyncEmitter extends Emitter { const event = { ...data, + token, waitUntil: (p: Promise): void => { if (Object.isFrozen(thenables)) { throw new Error('waitUntil can NOT be called asynchronous'); diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index 78ffb23b51f90..6109683d1f244 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event, AsyncEmitter, IWaitUntil } from 'vs/base/common/event'; +import { Emitter, Event, AsyncEmitter, IWaitUntil, IWaitUntilData } from 'vs/base/common/event'; import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -189,7 +189,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ return undefined; } - private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, timeout: number, token: CancellationToken): Promise { + private async _fireWillEvent(emitter: AsyncEmitter, data: IWaitUntilData, timeout: number, token: CancellationToken): Promise { const extensionNames = new Set(); const edits: WorkspaceEdit[] = []; diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index afdb41a05051c..431e536255e76 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -10554,6 +10554,11 @@ declare module 'vscode' { */ export interface FileWillCreateEvent { + /** + * A cancellation token. + */ + readonly token: CancellationToken; + /** * The files that are going to be created. */ @@ -10609,6 +10614,11 @@ declare module 'vscode' { */ export interface FileWillDeleteEvent { + /** + * A cancellation token. + */ + readonly token: CancellationToken; + /** * The files that are going to be deleted. */ @@ -10664,6 +10674,11 @@ declare module 'vscode' { */ export interface FileWillRenameEvent { + /** + * A cancellation token. + */ + readonly token: CancellationToken; + /** * The files that are going to be renamed. */ From 26ccfac24517c22561b3a1ceab170b326501ccdc Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 7 Dec 2021 11:32:34 +0100 Subject: [PATCH 0345/2210] Fix command line building for linux -> Windows remote Part of #5989 --- .../contrib/tasks/browser/terminalTaskSystem.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 2add2cd37d21a..f38489b27f83d 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1080,11 +1080,11 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } let shellArgs = Array.isArray(shellLaunchConfig.args!) ? shellLaunchConfig.args!.slice(0) : [shellLaunchConfig.args!]; let toAdd: string[] = []; - let commandLine = this.buildShellCommandLine(platform, shellLaunchConfig.executable!, shellOptions, command, originalCommand, args); + let basename = path.posix.basename((await this.pathService.fileURI(shellLaunchConfig.executable!)).path).toLowerCase(); + let commandLine = this.buildShellCommandLine(platform, basename, shellOptions, command, originalCommand, args); let windowsShellArgs: boolean = false; if (platform === Platform.Platform.Windows) { windowsShellArgs = true; - let basename = path.posix.basename((await this.pathService.fileURI(shellLaunchConfig.executable!)).path).toLowerCase(); // If we don't have a cwd, then the terminal uses the home dir. const userHome = await this.pathService.userHome(); if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath)))) { @@ -1337,8 +1337,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } private buildShellCommandLine(platform: Platform.Platform, shellExecutable: string, shellOptions: ShellConfiguration | undefined, command: CommandString, originalCommand: CommandString | undefined, args: CommandString[]): string { - let basename = path.parse(shellExecutable).name.toLowerCase(); - let shellQuoteOptions = this.getQuotingOptions(basename, shellOptions, platform); + let shellQuoteOptions = this.getQuotingOptions(shellExecutable, shellOptions, platform); function needsQuotes(value: string): boolean { if (value.length >= 2) { @@ -1425,9 +1424,9 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let commandLine = result.join(' '); // There are special rules quoted command line in cmd.exe if (platform === Platform.Platform.Windows) { - if (basename === 'cmd' && commandQuoted && argQuoted) { + if (shellExecutable === 'cmd' && commandQuoted && argQuoted) { commandLine = '"' + commandLine + '"'; - } else if ((basename === 'powershell' || basename === 'pwsh') && commandQuoted) { + } else if ((shellExecutable === 'powershell' || shellExecutable === 'pwsh') && commandQuoted) { commandLine = '& ' + commandLine; } } From f5ecc176c9f59b47dbc187053b0ef08959cfd215 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 7 Dec 2021 11:35:13 +0100 Subject: [PATCH 0346/2210] Implements dynamic dependency check for both get and _read. --- src/vs/editor/common/config/commonEditorConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 8e1801c507e99..5ace0b8d87b4b 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -70,7 +70,7 @@ export class ComputedEditorOptions implements IComputedEditorOptions { return this._values[id]; } public get(id: T): FindComputedEditorOptionValueById { - return this._values[id]; + return this._read(id); } public _write(id: EditorOption, value: T): void { this._values[id] = value; From b54608012b5b24e4ff85c28f1a7ee71cc29cc9b4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 7 Dec 2021 12:40:13 +0100 Subject: [PATCH 0347/2210] bracketPairs -> bracketPairsTextModelPart --- src/vs/editor/common/model.ts | 4 ++-- .../bracketPairs.ts | 2 +- .../bracketPairsImpl.ts | 6 +++--- .../bracketPairsTree/ast.ts | 0 .../bracketPairsTree/beforeEditPositionMapper.ts | 0 .../bracketPairsTree/bracketPairsTree.ts | 2 +- .../bracketPairsTree/brackets.ts | 0 .../bracketPairsTree/concat23Trees.ts | 0 .../bracketPairsTree/length.ts | 0 .../bracketPairsTree/nodeReader.ts | 0 .../bracketPairsTree/parser.ts | 0 .../bracketPairsTree/smallImmutableSet.ts | 0 .../bracketPairsTree/tokenizer.ts | 0 .../colorizedBracketPairsDecorationProvider.ts | 2 +- src/vs/editor/common/model/textModel.ts | 12 ++++++------ .../gotoSymbol/link/goToDefinitionAtPosition.ts | 2 +- .../beforeEditPositionMapper.test.ts | 4 ++-- .../model/bracketPairColorizer/brackets.test.ts | 6 +++--- .../model/bracketPairColorizer/concat23Trees.test.ts | 6 +++--- .../getBracketPairsInRange.test.ts | 2 +- .../common/model/bracketPairColorizer/length.test.ts | 2 +- .../bracketPairColorizer/smallImmutableSet.test.ts | 2 +- .../model/bracketPairColorizer/tokenizer.test.ts | 8 ++++---- .../test/common/model/textModelWithTokens.test.ts | 2 +- 24 files changed, 31 insertions(+), 31 deletions(-) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairs.ts (98%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsImpl.ts (99%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/ast.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/beforeEditPositionMapper.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/bracketPairsTree.ts (99%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/brackets.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/concat23Trees.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/length.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/nodeReader.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/parser.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/smallImmutableSet.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/bracketPairsTree/tokenizer.ts (100%) rename src/vs/editor/common/model/{bracketPairs => bracketPairsTextModelPart}/colorizedBracketPairsDecorationProvider.ts (99%) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 605714dfd3356..90c9b066b161b 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -18,7 +18,7 @@ import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; import { TextChange } from 'vs/editor/common/model/textChange'; import { equals } from 'vs/base/common/objects'; -import { IBracketPairs } from 'vs/editor/common/model/bracketPairs/bracketPairs'; +import { IBracketPairsTextModelPart } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; /** * Vertical Lane in the overview ruler of the editor. @@ -1293,7 +1293,7 @@ export interface ITextModel { * Returns an object that can be used to query brackets. * @internal */ - get bracketPairs(): IBracketPairs; + get bracketPairs(): IBracketPairsTextModelPart; } /** diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairs.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairs.ts similarity index 98% rename from src/vs/editor/common/model/bracketPairs/bracketPairs.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairs.ts index 75f5b9a3b6b4a..66f4a736ef76d 100644 --- a/src/vs/editor/common/model/bracketPairs/bracketPairs.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairs.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -export interface IBracketPairs { +export interface IBracketPairsTextModelPart { /** * Is fired when bracket pairs change, either due to a text or a settings change. */ diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsImpl.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts similarity index 99% rename from src/vs/editor/common/model/bracketPairs/bracketPairsImpl.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts index 8680054fccd5f..0fa58943718cf 100644 --- a/src/vs/editor/common/model/bracketPairs/bracketPairsImpl.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts @@ -8,15 +8,15 @@ import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { BracketPairsTree } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/bracketPairsTree'; -import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBracketPairs, IFoundBracket } from 'vs/editor/common/model/bracketPairs/bracketPairs'; +import { BracketPairsTree } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree'; +import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBracketPairsTextModelPart, IFoundBracket } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports'; import { RichEditBrackets, BracketsUtils, RichEditBracket } from 'vs/editor/common/modes/supports/richEditBrackets'; -export class BracketPairs extends Disposable implements IBracketPairs { +export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart { private readonly bracketPairsTree = this._register(new MutableDisposable>()); private readonly onDidChangeEmitter = new Emitter(); diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/ast.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/ast.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast.ts diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/beforeEditPositionMapper.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/beforeEditPositionMapper.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/bracketPairsTree.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts similarity index 99% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/bracketPairsTree.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts index 450d570e6fe7b..0a1ef68bf5adc 100644 --- a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/bracketPairsTree.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts @@ -7,7 +7,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { BracketInfo, BracketPairWithMinIndentationInfo } from 'vs/editor/common/model/bracketPairs/bracketPairs'; +import { BracketInfo, BracketPairWithMinIndentationInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; import { BackgroundTokenizationState, TextModel } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { ResolvedLanguageConfiguration } from 'vs/editor/common/modes/languageConfigurationRegistry'; diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/brackets.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/brackets.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/concat23Trees.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/concat23Trees.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees.ts diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/length.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/length.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/nodeReader.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/nodeReader.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/nodeReader.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/nodeReader.ts diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/parser.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/parser.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser.ts diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts diff --git a/src/vs/editor/common/model/bracketPairs/bracketPairsTree/tokenizer.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts similarity index 100% rename from src/vs/editor/common/model/bracketPairs/bracketPairsTree/tokenizer.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts diff --git a/src/vs/editor/common/model/bracketPairs/colorizedBracketPairsDecorationProvider.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.ts similarity index 99% rename from src/vs/editor/common/model/bracketPairs/colorizedBracketPairsDecorationProvider.ts rename to src/vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.ts index adf56d6e955a4..6bb8559c839b9 100644 --- a/src/vs/editor/common/model/bracketPairs/colorizedBracketPairsDecorationProvider.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.ts @@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; import { BracketPairColorizationOptions, IModelDecoration } from 'vs/editor/common/model'; -import { BracketInfo } from 'vs/editor/common/model/bracketPairs/bracketPairs'; +import { BracketInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; import { DecorationProvider } from 'vs/editor/common/model/decorationProvider'; import { TextModel } from 'vs/editor/common/model/textModel'; import { diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index bcaaf7074f10b..477967df90779 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -38,9 +38,9 @@ import { Constants } from 'vs/base/common/uint'; import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; import { listenStream } from 'vs/base/common/stream'; import { ArrayQueue, findLast } from 'vs/base/common/arrays'; -import { BracketPairInfo, IBracketPairs } from 'vs/editor/common/model/bracketPairs/bracketPairs'; -import { BracketPairs } from 'vs/editor/common/model/bracketPairs/bracketPairsImpl'; -import { ColorizedBracketPairsDecorationProvider } from 'vs/editor/common/model/bracketPairs/colorizedBracketPairsDecorationProvider'; +import { BracketPairInfo, IBracketPairsTextModelPart } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; +import { BracketPairsTextModelPart } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl'; +import { ColorizedBracketPairsDecorationProvider } from 'vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider'; import { DecorationProvider } from 'vs/editor/common/model/decorationProvider'; import { CursorColumns } from 'vs/editor/common/controller/cursorColumns'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -297,8 +297,8 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati private readonly _tokenization: TextModelTokenization; //#endregion - private readonly _bracketPairColorizer: BracketPairs; - public get bracketPairs(): IBracketPairs { return this._bracketPairColorizer; } + private readonly _bracketPairColorizer: BracketPairsTextModelPart; + public get bracketPairs(): IBracketPairsTextModelPart { return this._bracketPairColorizer; } private _backgroundTokenizationState = BackgroundTokenizationState.Uninitialized; public get backgroundTokenizationState(): BackgroundTokenizationState { @@ -399,7 +399,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati this._tokens2 = new TokensStore2(this._modeService.languageIdCodec); this._tokenization = new TextModelTokenization(this, this._modeService.languageIdCodec); - this._bracketPairColorizer = this._register(new BracketPairs(this, this._languageConfigurationService)); + this._bracketPairColorizer = this._register(new BracketPairsTextModelPart(this, this._languageConfigurationService)); this._decorationProvider = this._register(new ColorizedBracketPairsDecorationProvider(this)); this._register(this._decorationProvider.onDidChange(() => { diff --git a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index c078803a8cfe0..bae5ecc623e5e 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -19,7 +19,7 @@ import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDeltaDecoration, ITextModel, IWordAtPosition } from 'vs/editor/common/model'; -import { IFoundBracket } from 'vs/editor/common/model/bracketPairs/bracketPairs'; +import { IFoundBracket } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts index b21d9f3e20487..5401b4fab6b55 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts @@ -7,8 +7,8 @@ import assert = require('assert'); import { splitLines } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { BeforeEditPositionMapper, TextEditInfo } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/beforeEditPositionMapper'; -import { Length, lengthOfString, lengthToObj, lengthToPosition, toLength } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/length'; +import { BeforeEditPositionMapper, TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper'; +import { Length, lengthOfString, lengthToObj, lengthToPosition, toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { test('Single-Line 1', () => { diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts index 5d47baa9122f8..7e2a28022cfca 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts @@ -5,9 +5,9 @@ import assert = require('assert'); import { DisposableStore } from 'vs/base/common/lifecycle'; -import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/brackets'; -import { SmallImmutableSet, DenseKeyProvider } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet'; -import { Token, TokenKind } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/tokenizer'; +import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets'; +import { SmallImmutableSet, DenseKeyProvider } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet'; +import { Token, TokenKind } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts index ca39ae3f3e3a2..f0b19c8a90f1c 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import assert = require('assert'); -import { AstNode, AstNodeKind, ListAstNode, TextAstNode } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/ast'; -import { toLength } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/length'; -import { concat23Trees } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/concat23Trees'; +import { AstNode, AstNodeKind, ListAstNode, TextAstNode } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast'; +import { toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; +import { concat23Trees } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees'; suite('Bracket Pair Colorizer - mergeItems', () => { test('Clone', () => { diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts index 7db18fa86e6ab..d8a6d1ea23680 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts @@ -7,7 +7,7 @@ import assert = require('assert'); import { Disposable, disposeOnReturn } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { BracketPairInfo } from 'vs/editor/common/model/bracketPairs/bracketPairs'; +import { BracketPairInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; import { LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts index 75a55bfaf4708..20e317d08431a 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert = require('assert'); -import { Length, lengthAdd, lengthDiffNonNegative, lengthToObj, toLength } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/length'; +import { Length, lengthAdd, lengthDiffNonNegative, lengthToObj, toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; suite('Bracket Pair Colorizer - Length', () => { function toStr(length: Length): string { diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts index f608e7da28b90..f36fe4db187c6 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert = require('assert'); -import { DenseKeyProvider, SmallImmutableSet } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet'; +import { DenseKeyProvider, SmallImmutableSet } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet'; suite('Bracket Pair Colorizer - ImmutableSet', () => { test('Basic', () => { diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts index 58acab5524e8c..6c2f478464a2a 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts @@ -6,10 +6,10 @@ import assert = require('assert'); import { DisposableStore } from 'vs/base/common/lifecycle'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; -import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/brackets'; -import { Length, lengthAdd, lengthsToRange, lengthZero } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/length'; -import { DenseKeyProvider } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/smallImmutableSet'; -import { TextBufferTokenizer, Token, Tokenizer, TokenKind } from 'vs/editor/common/model/bracketPairs/bracketPairsTree/tokenizer'; +import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets'; +import { Length, lengthAdd, lengthsToRange, lengthZero } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; +import { DenseKeyProvider } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet'; +import { TextBufferTokenizer, Token, Tokenizer, TokenKind } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IState, ITokenizationSupport, LanguageId, MetadataConsts, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/modes'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 951dc420a7353..b48f40e57a41c 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -8,7 +8,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; -import { IFoundBracket } from 'vs/editor/common/model/bracketPairs/bracketPairs'; +import { IFoundBracket } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ITokenizationSupport, MetadataConsts, TokenizationRegistry, StandardTokenType } from 'vs/editor/common/modes'; import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration'; From 3eee722d2ef8b72ea9e74d0681835549974533fa Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 7 Dec 2021 13:12:41 +0100 Subject: [PATCH 0348/2210] Reuse `httpsWebWorkerExtensionHostIframe` when running locally in the desktop (by allowing 'self'). This change does not relax in any way the CSP in the web case since `https:` was allowed before. --- .../browser/webWorkerExtensionHost.ts | 3 +- .../fileWebWorkerExtensionHostIframe.html | 65 ------------------- .../httpsWebWorkerExtensionHostIframe.html | 2 +- 3 files changed, 2 insertions(+), 68 deletions(-) delete mode 100644 src/vs/workbench/services/extensions/worker/fileWebWorkerExtensionHostIframe.html diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 617c53b956aa5..83d80d4cd2e95 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -142,7 +142,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost this._protocolPromise = this._startOutsideIframe(); } } else { - const fileExtensionHostIframeSrc = FileAccess.asBrowserUri('../worker/fileWebWorkerExtensionHostIframe.html', require); + const fileExtensionHostIframeSrc = FileAccess.asBrowserUri('../worker/httpsWebWorkerExtensionHostIframe.html', require); this._protocolPromise = this._startInsideIframe(`${fileExtensionHostIframeSrc.toString(true)}?`); } this._protocolPromise.then(protocol => this._protocol = protocol); @@ -160,7 +160,6 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost const vscodeWebWorkerExtHostId = generateUuid(); iframe.setAttribute('src', `${webWorkerExtensionHostIframeSrc}&vscodeWebWorkerExtHostId=${vscodeWebWorkerExtHostId}`); - // iframe.setAttribute('src', `${webWorkerExtensionHostIframeSrc}`); const barrier = new Barrier(); let port!: MessagePort; diff --git a/src/vs/workbench/services/extensions/worker/fileWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/fileWebWorkerExtensionHostIframe.html deleted file mode 100644 index c7cc0c58aab21..0000000000000 --- a/src/vs/workbench/services/extensions/worker/fileWebWorkerExtensionHostIframe.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - diff --git a/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html index abe5b94e81f20..d62ba9c0c2249 100644 --- a/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html +++ b/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html @@ -1,7 +1,7 @@ - + - - diff --git a/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html similarity index 100% rename from src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html rename to src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html From acbb31b21328a2ff8163734600f9b553706b4bc6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Dec 2021 14:43:59 +0100 Subject: [PATCH 0356/2210] :lipstick: --- src/vs/base/common/map.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 9432c915627c0..7fe1402cdafd4 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -130,6 +130,7 @@ export class ConfigKeysIterator implements IKeyIterator { export class PathIterator implements IKeyIterator { private _value!: string; + private _valueLen!: number; private _from!: number; private _to!: number; @@ -139,21 +140,29 @@ export class PathIterator implements IKeyIterator { ) { } reset(key: string): this { - this._value = key.replace(/\\$|\/$/, ''); this._from = 0; this._to = 0; + this._value = key; + this._valueLen = key.length; + for (let pos = key.length - 1; pos >= 0; pos--, this._valueLen--) { + const ch = this._value.charCodeAt(pos); + if (!(ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash)) { + break; + } + } + return this.next(); } hasNext(): boolean { - return this._to < this._value.length; + return this._to < this._valueLen; } next(): this { // this._data = key.split(/[\\/]/).filter(s => !!s); this._from = this._to; let justSeps = true; - for (; this._to < this._value.length; this._to++) { + for (; this._to < this._valueLen; this._to++) { const ch = this._value.charCodeAt(this._to); if (ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash) { if (justSeps) { From a2da2566b591404c8844d0039e03b0ec253e3f47 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 7 Dec 2021 15:03:07 +0100 Subject: [PATCH 0357/2210] Fixes #137866: Keep the web worker extension host iframe origin stable by storing it in local workspace storage --- .../extensions/browser/webWorkerExtensionHost.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 994b5ae7dbc5a..602b4bf4e801f 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -29,6 +29,7 @@ import { canceled, onUnexpectedError } from 'vs/base/common/errors'; import { Barrier } from 'vs/base/common/async'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { FileAccess } from 'vs/base/common/network'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; export interface IWebWorkerExtensionHostInitData { readonly autoStart: boolean; @@ -65,6 +66,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IProductService private readonly _productService: IProductService, @ILayoutService private readonly _layoutService: ILayoutService, + @IStorageService private readonly _storageService: IStorageService, ) { super(); this.lazyStart = lazyStart; @@ -83,9 +85,16 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost const commit = this._productService.commit; const quality = this._productService.quality; if (webEndpointUrlTemplate && commit && quality) { + // Try to keep the web worker extension host iframe origin stable by storing it in workspace storage + const key = 'webWorkerExtensionHostIframeStableOriginUUID'; + let stableOriginUUID = this._storageService.get(key, StorageScope.WORKSPACE); + if (typeof stableOriginUUID === 'undefined') { + stableOriginUUID = generateUuid(); + this._storageService.store(key, stableOriginUUID, StorageScope.WORKSPACE, StorageTarget.MACHINE); + } const baseUrl = ( webEndpointUrlTemplate - .replace('{{uuid}}', generateUuid()) + .replace('{{uuid}}', stableOriginUUID) .replace('{{commit}}', commit) .replace('{{quality}}', quality) ); From 95dcb89a92b90795f6b270bc867d8f47d05922d5 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 7 Dec 2021 15:09:32 +0100 Subject: [PATCH 0358/2210] Update grammars --- extensions/cpp/cgmanifest.json | 2 +- extensions/dart/cgmanifest.json | 2 +- extensions/dart/syntaxes/dart.tmLanguage.json | 12 +- extensions/diff/cgmanifest.json | 2 +- extensions/fsharp/cgmanifest.json | 2 +- .../fsharp/syntaxes/fsharp.tmLanguage.json | 14 +- extensions/git-base/cgmanifest.json | 2 +- extensions/julia/cgmanifest.json | 4 +- .../julia/syntaxes/julia.tmLanguage.json | 257 +++--------------- extensions/php/cgmanifest.json | 4 +- extensions/r/cgmanifest.json | 2 +- extensions/r/syntaxes/r.tmLanguage.json | 23 +- extensions/scss/cgmanifest.json | 4 +- 13 files changed, 92 insertions(+), 238 deletions(-) diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index f2bf269a5f46d..51ed002f31e8f 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jeff-hykin/cpp-textmate-grammar", "repositoryUrl": "https://github.com/jeff-hykin/cpp-textmate-grammar", - "commitHash": "db3f4e4a5d8335b2f6d689bec490c23f8313630f" + "commitHash": "0ef79f098ed80ce5a86be4ed40f99ebcdbac4895" } }, "license": "MIT", diff --git a/extensions/dart/cgmanifest.json b/extensions/dart/cgmanifest.json index 9a778f2ced21e..fb2bd89d625be 100644 --- a/extensions/dart/cgmanifest.json +++ b/extensions/dart/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dart-lang/dart-syntax-highlight", "repositoryUrl": "https://github.com/dart-lang/dart-syntax-highlight", - "commitHash": "1fa12423de71bcc75f68371ca4debaebdd989c20" + "commitHash": "a93646fe6e552d1984c24fd31b1c07dcb3ce7c21" } }, "licenseDetail": [ diff --git a/extensions/dart/syntaxes/dart.tmLanguage.json b/extensions/dart/syntaxes/dart.tmLanguage.json index cfff8dc963d4f..b491ff8656cff 100644 --- a/extensions/dart/syntaxes/dart.tmLanguage.json +++ b/extensions/dart/syntaxes/dart.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/1fa12423de71bcc75f68371ca4debaebdd989c20", + "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/a93646fe6e552d1984c24fd31b1c07dcb3ce7c21", "name": "Dart", "scopeName": "source.dart", "patterns": [ @@ -230,7 +230,7 @@ "class-identifier": { "patterns": [ { - "match": "(?]|, )+>)?|bool\\b|num\\b|int\\b|double\\b|dynamic\\b|(void)\\b)", + "match": "(??]|,\\s*|\\s+extends\\s+)+>)?|bool\\b|num\\b|int\\b|double\\b|dynamic\\b|(void)\\b)", "captures": { "1": { "name": "support.class.dart" @@ -252,7 +252,7 @@ "function-identifier": { "patterns": [ { - "match": "([_$]*[a-z][a-zA-Z0-9_$]*)(<(?:[a-zA-Z0-9_$<>]|, )+>)?(\\(|\\s+=>)", + "match": "([_$]*[a-z][a-zA-Z0-9_$]*)(<(?:[a-zA-Z0-9_$<>?]|,\\s*|\\s+extends\\s+)+>)?(\\(|\\s+=>)", "captures": { "1": { "name": "entity.name.function.dart" @@ -286,7 +286,11 @@ "include": "#class-identifier" }, { - "match": "\\s*,\\s*" + "match": "[\\s,]+" + }, + { + "name": "keyword.declaration.dart", + "match": "extends" } ] }, diff --git a/extensions/diff/cgmanifest.json b/extensions/diff/cgmanifest.json index 04d6573c95ecb..a06c6c7f10eef 100644 --- a/extensions/diff/cgmanifest.json +++ b/extensions/diff/cgmanifest.json @@ -29,4 +29,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index b898a38669c21..c6bf0cd413978 100644 --- a/extensions/fsharp/cgmanifest.json +++ b/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "fc4cac6d9bc1787f54ce48bbc77bcbb1de8160ff" + "commitHash": "bba27391e61090035449b5c1e5c4b9d396bc4c9b" } }, "license": "MIT", diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index ca06a19c2c26a..c388ab00750a9 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/ionide/ionide-fsgrammar/commit/fc4cac6d9bc1787f54ce48bbc77bcbb1de8160ff", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/bba27391e61090035449b5c1e5c4b9d396bc4c9b", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -527,8 +527,8 @@ "patterns": [ { "name": "comment.block.markdown.fsharp", - "begin": "^\\s*(\\(\\*\\*(?!\\)))(?!\\*\\))$", - "while": "^(?!\\s*\\*\\)$)", + "begin": "^\\s*(\\(\\*\\*(?!\\)))((?!\\*\\)).)*$", + "while": "^(?!\\s*(\\*)+\\)$)", "beginCaptures": { "1": { "name": "comment.block.fsharp" @@ -572,7 +572,7 @@ }, { "name": "comment.block.markdown.fsharp.end", - "match": "(\\*\\))", + "match": "((? Date: Tue, 7 Dec 2021 15:14:34 +0100 Subject: [PATCH 0359/2210] No need for trusted types in extHost worker and worker extension loading, https://github.com/microsoft/vscode/issues/138588 --- .../electron-browser/workbench/workbench.html | 2 +- .../electron-sandbox/workbench/workbench.html | 2 +- .../api/worker/extHostExtensionService.ts | 37 +------------------ .../extensions/worker/extensionHostWorker.ts | 7 +--- 4 files changed, 5 insertions(+), 43 deletions(-) diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index d133d7a614bd1..0efd653996039 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -4,7 +4,7 @@ - + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index d133d7a614bd1..0efd653996039 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -4,7 +4,7 @@ - + diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index 8eed290c666fa..c219a2eec641d 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -14,34 +14,6 @@ import { timeout } from 'vs/base/common/async'; import { MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; import { FileAccess } from 'vs/base/common/network'; -namespace TrustedFunction { - - // workaround a chrome issue not allowing to create new functions - // see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor - const ttpTrustedFunction = self.trustedTypes?.createPolicy('TrustedFunctionWorkaround', { - createScript: (_, ...args: string[]) => { - args.forEach((arg) => { - if (!self.trustedTypes?.isScript(arg)) { - throw new Error('TrustedScripts only, please'); - } - }); - // NOTE: This is insecure without parsing the arguments and body, - // Malicious inputs can escape the function body and execute immediately! - const fnArgs = args.slice(0, -1).join(','); - const fnBody = args.pop()!.toString(); - const body = `(function anonymous(${fnArgs}) {${fnBody}\n})`; - return body; - } - }); - - export function create(...args: string[]): Function { - if (!ttpTrustedFunction) { - return new Function(...args); - } - return self.eval(ttpTrustedFunction.createScript('', ...args) as unknown as string); - } -} - class WorkerRequireInterceptor extends RequireInterceptor { _installInterceptor() { } @@ -65,8 +37,6 @@ class WorkerRequireInterceptor extends RequireInterceptor { export class ExtHostExtensionService extends AbstractExtHostExtensionService { readonly extensionRuntime = ExtensionRuntime.Webworker; - private static _ttpExtensionScripts = self.trustedTypes?.createPolicy('ExtensionScripts', { createScript: source => source }); - private _fakeModules?: WorkerRequireInterceptor; protected async _beforeAlmostReadyToRunExtensions(): Promise { @@ -109,12 +79,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { const fullSource = `${source}\n//# sourceURL=${sourceURL}`; let initFn: Function; try { - initFn = TrustedFunction.create( - ExtHostExtensionService._ttpExtensionScripts?.createScript('module') as unknown as string ?? 'module', - ExtHostExtensionService._ttpExtensionScripts?.createScript('exports') as unknown as string ?? 'exports', - ExtHostExtensionService._ttpExtensionScripts?.createScript('require') as unknown as string ?? 'require', - ExtHostExtensionService._ttpExtensionScripts?.createScript(fullSource) as unknown as string ?? fullSource - ); + initFn = new Function('module', 'exports', 'require', fullSource); } catch (err) { if (extensionId) { console.error(`Loading code for extension ${extensionId.value} failed: ${err.message}`); diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index 21ebf4db806b7..f85ca21d421f2 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -36,7 +36,6 @@ declare namespace self { let importScripts: any; let fetch: _Fetch; let XMLHttpRequest: any; - let trustedTypes: any; } const nativeClose = self.close.bind(self); @@ -81,7 +80,6 @@ self.addEventListener = () => console.trace(`'addEventListener' has been blocked (self)['webkitResolveLocalFileSystemURL'] = undefined; if ((self).Worker) { - const ttPolicy = (self).trustedTypes?.createPolicy('extensionHostWorker', { createScriptURL: (value: string) => value }); // make sure new Worker(...) always uses blob: (to maintain current origin) const _Worker = (self).Worker; @@ -119,8 +117,7 @@ if ((self).Worker) { nativeImportScripts(...urls.map(asWorkerBrowserUrl)); }; - const ttPolicy = self.trustedTypes ? self.trustedTypes.createPolicy('extensionHostWorker', { createScriptURL: (value: string) => value }) : undefined; - nativeImportScripts(ttPolicy ? ttPolicy.createScriptURL(workerUrl) : workerUrl); + nativeImportScripts(workerUrl); }).toString(); const js = `(${bootstrapFnSource}('${stringUrl}'))`; @@ -128,7 +125,7 @@ if ((self).Worker) { options.name = options.name || path.basename(stringUrl.toString()); const blob = new Blob([js], { type: 'application/javascript' }); const blobUrl = URL.createObjectURL(blob); - return new _Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) : blobUrl, options); + return new _Worker(blobUrl, options); }; } else { From 5cfed19ad7b2a053c024978a469096e56b2345b3 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 7 Dec 2021 16:18:02 +0100 Subject: [PATCH 0360/2210] Add trace logging to terminal task system Part of #134431 --- .../workbench/contrib/tasks/browser/terminalTaskSystem.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index f38489b27f83d..f91db29425927 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -243,7 +243,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return this._onDidStateChange.event; } - public log(value: string): void { + private log(value: string): void { this.appendOutput(value + '\n'); } @@ -1202,6 +1202,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { // Even if an existing terminal is found, the split can fail if the terminal width is too small. for (const terminal of values(this.terminals)) { if (terminal.group === group) { + this.logService.trace(`Found terminal to split for group ${group}`); const originalInstance = terminal.terminal; const result = await this.terminalService.createTerminal({ location: { parentTerminal: originalInstance }, config: launchConfigs }); if (result) { @@ -1209,9 +1210,12 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } } } + this.logService.trace(`No terminal found to split for group ${group}`); } // Either no group is used, no terminal with the group exists or splitting an existing terminal failed. - return this.terminalService.createTerminal({ location: TerminalLocation.Panel, config: launchConfigs }); + const createdTerminal = await this.terminalService.createTerminal({ location: TerminalLocation.Panel, config: launchConfigs }); + this.logService.trace('Created a new task terminal'); + return createdTerminal; } private async createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver, workspaceFolder: IWorkspaceFolder | undefined): Promise<[ITerminalInstance | undefined, string | undefined, TaskError | undefined]> { From 74aa0cb3ad01b640b58993272b5bad8233fe1419 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 7 Dec 2021 17:21:05 +0100 Subject: [PATCH 0361/2210] Implements runAndSubscribe utility function. --- src/vs/base/common/event.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 605eb2d2b4a61..0126930643542 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -337,6 +337,11 @@ export namespace Event { export function toPromise(event: Event): Promise { return new Promise(resolve => once(event)(resolve)); } + + export function runAndSubscribe(event: Event, handler: (e: T | undefined) => any): IDisposable { + handler(undefined); + return event(e => handler(e)); + } } export type Listener = [(e: T) => void, any] | ((e: T) => void); From 7170786e0f6e49802e774b35ff91e0ef97a5b092 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 7 Dec 2021 17:23:25 +0100 Subject: [PATCH 0362/2210] Adopts ILanguageConfigurationService where LanguageConfigurationRegistry was used. --- .../modes/languageConfigurationRegistry.ts | 13 +++--- .../contrib/comment/blockCommentCommand.ts | 10 +++-- src/vs/editor/contrib/comment/comment.ts | 6 ++- .../contrib/comment/lineCommentCommand.ts | 20 +++++---- .../comment/test/blockCommentCommand.test.ts | 7 +-- .../comment/test/lineCommentCommand.test.ts | 23 +++++----- src/vs/editor/contrib/folding/folding.ts | 23 +++++----- .../contrib/folding/indentRangeProvider.ts | 13 +++--- .../editor/contrib/indentation/indentation.ts | 10 ++--- .../contrib/linkedEditing/linkedEditing.ts | 19 +++++--- .../test/wordOperations.test.ts | 11 ++++- .../contrib/wordOperations/wordOperations.ts | 5 ++- .../contrib/wordPartOperations/test/utils.ts | 23 ++++++++++ .../test/wordPartOperations.test.ts | 10 ++++- .../browser/snippetCompletionProvider.ts | 7 +-- .../snippets/browser/snippetsService.ts | 3 +- .../test/browser/snippetsService.test.ts | 43 ++++++++++--------- 17 files changed, 156 insertions(+), 90 deletions(-) create mode 100644 src/vs/editor/contrib/wordPartOperations/test/utils.ts diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index 704e51f7866b7..f5cf6a029edbf 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -204,11 +204,6 @@ export class LanguageConfigurationRegistryImpl { return entries?.getResolvedConfiguration() || null; } - public getIndentationRules(languageId: string): IndentationRule | null { - const value = this.getLanguageConfiguration(languageId); - return value ? value.indentationRules || null : null; - } - // begin electricCharacter private _getElectricCharacterSupport(languageId: string): BracketElectricCharacterSupport | null { @@ -797,6 +792,9 @@ export class LanguageConfigurationRegistryImpl { } } +/** + * @deprecated Use ILanguageConfigurationService instead. +*/ export const LanguageConfigurationRegistry = new LanguageConfigurationRegistryImpl(); class ComposedLanguageConfiguration { @@ -969,6 +967,11 @@ export class ResolvedLanguageConfiguration { return this._electricCharacter; } + public getAutoClosingPairs(): AutoClosingPairs { + const characterPairSupport = this.characterPair; + return new AutoClosingPairs(characterPairSupport ? characterPairSupport.getAutoClosingPairs() : []); + } + public onEnter( autoIndent: EditorAutoIndentStrategy, previousLineText: string, diff --git a/src/vs/editor/contrib/comment/blockCommentCommand.ts b/src/vs/editor/contrib/comment/blockCommentCommand.ts index 5e684e5eb1871..68d55986bf8df 100644 --- a/src/vs/editor/contrib/comment/blockCommentCommand.ts +++ b/src/vs/editor/contrib/comment/blockCommentCommand.ts @@ -10,7 +10,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; export class BlockCommentCommand implements ICommand { @@ -18,7 +18,11 @@ export class BlockCommentCommand implements ICommand { private readonly _insertSpace: boolean; private _usedEndToken: string | null; - constructor(selection: Selection, insertSpace: boolean) { + constructor( + selection: Selection, + insertSpace: boolean, + private readonly languageConfigurationService: ILanguageConfigurationService + ) { this._selection = selection; this._insertSpace = insertSpace; this._usedEndToken = null; @@ -168,7 +172,7 @@ export class BlockCommentCommand implements ICommand { model.tokenizeIfCheap(startLineNumber); const languageId = model.getLanguageIdAtPosition(startLineNumber, startColumn); - const config = LanguageConfigurationRegistry.getComments(languageId); + const config = this.languageConfigurationService.getLanguageConfiguration(languageId).comments; if (!config || !config.blockCommentStartToken || !config.blockCommentEndToken) { // Mode does not support block comments return; diff --git a/src/vs/editor/contrib/comment/comment.ts b/src/vs/editor/contrib/comment/comment.ts index f91b6637c2474..97d3f443b46c3 100644 --- a/src/vs/editor/contrib/comment/comment.ts +++ b/src/vs/editor/contrib/comment/comment.ts @@ -10,6 +10,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { ICommand } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { BlockCommentCommand } from 'vs/editor/contrib/comment/blockCommentCommand'; import { LineCommentCommand, Type } from 'vs/editor/contrib/comment/lineCommentCommand'; import * as nls from 'vs/nls'; @@ -55,9 +56,11 @@ abstract class CommentLineAction extends EditorAction { } } + const languageConfigurationService = accessor.get(ILanguageConfigurationService); for (const selection of selections) { commands.push(new LineCommentCommand( + languageConfigurationService, selection.selection, modelOptions.tabSize, this._type, @@ -159,8 +162,9 @@ class BlockCommentAction extends EditorAction { const commentsOptions = editor.getOption(EditorOption.comments); const commands: ICommand[] = []; const selections = editor.getSelections(); + const languageConfigurationService = accessor.get(ILanguageConfigurationService); for (const selection of selections) { - commands.push(new BlockCommentCommand(selection, commentsOptions.insertSpace)); + commands.push(new BlockCommentCommand(selection, commentsOptions.insertSpace, languageConfigurationService)); } editor.pushUndoStop(); diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index 6b930dc6b7a27..413d6d1134c39 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -12,7 +12,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { BlockCommentCommand } from 'vs/editor/contrib/comment/blockCommentCommand'; export interface IInsertionPoint { @@ -60,12 +60,13 @@ export class LineCommentCommand implements ICommand { private _ignoreFirstLine: boolean; constructor( + private readonly languageConfigurationService: ILanguageConfigurationService, selection: Selection, tabSize: number, type: Type, insertSpace: boolean, ignoreEmptyLines: boolean, - ignoreFirstLine?: boolean + ignoreFirstLine?: boolean, ) { this._selection = selection; this._tabSize = tabSize; @@ -82,12 +83,12 @@ export class LineCommentCommand implements ICommand { * Do an initial pass over the lines and gather info about the line comment string. * Returns null if any of the lines doesn't support a line comment string. */ - public static _gatherPreflightCommentStrings(model: ITextModel, startLineNumber: number, endLineNumber: number): ILinePreflightData[] | null { + private static _gatherPreflightCommentStrings(model: ITextModel, startLineNumber: number, endLineNumber: number, languageConfigurationService: ILanguageConfigurationService): ILinePreflightData[] | null { model.tokenizeIfCheap(startLineNumber); const languageId = model.getLanguageIdAtPosition(startLineNumber, 1); - const config = LanguageConfigurationRegistry.getComments(languageId); + const config = languageConfigurationService.getLanguageConfiguration(languageId).comments; const commentStr = (config ? config.lineCommentToken : null); if (!commentStr) { // Mode does not support line comments @@ -111,7 +112,7 @@ export class LineCommentCommand implements ICommand { * Analyze lines and decide which lines are relevant and what the toggle should do. * Also, build up several offsets and lengths useful in the generation of editor operations. */ - public static _analyzeLines(type: Type, insertSpace: boolean, model: ISimpleModel, lines: ILinePreflightData[], startLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean): IPreflightData { + public static _analyzeLines(type: Type, insertSpace: boolean, model: ISimpleModel, lines: ILinePreflightData[], startLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean, languageConfigurationService: ILanguageConfigurationService): IPreflightData { let onlyWhitespaceLines = true; let shouldRemoveComments: boolean; @@ -187,15 +188,15 @@ export class LineCommentCommand implements ICommand { /** * Analyze all lines and decide exactly what to do => not supported | insert line comments | remove line comments */ - public static _gatherPreflightData(type: Type, insertSpace: boolean, model: ITextModel, startLineNumber: number, endLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean): IPreflightData { - const lines = LineCommentCommand._gatherPreflightCommentStrings(model, startLineNumber, endLineNumber); + public static _gatherPreflightData(type: Type, insertSpace: boolean, model: ITextModel, startLineNumber: number, endLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean, languageConfigurationService: ILanguageConfigurationService): IPreflightData { + const lines = LineCommentCommand._gatherPreflightCommentStrings(model, startLineNumber, endLineNumber, languageConfigurationService); if (lines === null) { return { supported: false }; } - return LineCommentCommand._analyzeLines(type, insertSpace, model, lines, startLineNumber, ignoreEmptyLines, ignoreFirstLine); + return LineCommentCommand._analyzeLines(type, insertSpace, model, lines, startLineNumber, ignoreEmptyLines, ignoreFirstLine, languageConfigurationService); } /** @@ -350,7 +351,8 @@ export class LineCommentCommand implements ICommand { s.startLineNumber, s.endLineNumber, this._ignoreEmptyLines, - this._ignoreFirstLine + this._ignoreFirstLine, + this.languageConfigurationService ); if (data.supported) { diff --git a/src/vs/editor/contrib/comment/test/blockCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/blockCommentCommand.test.ts index cb862b201a756..a21f7948b66a7 100644 --- a/src/vs/editor/contrib/comment/test/blockCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/blockCommentCommand.test.ts @@ -6,10 +6,11 @@ import { Selection } from 'vs/editor/common/core/selection'; import { BlockCommentCommand } from 'vs/editor/contrib/comment/blockCommentCommand'; import { testCommand } from 'vs/editor/test/browser/testCommand'; import { CommentMode } from 'vs/editor/test/common/commentMode'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; function testBlockCommentCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { let mode = new CommentMode({ lineComment: '!@#', blockComment: ['<0', '0>'] }); - testCommand(lines, mode.languageId, selection, (sel) => new BlockCommentCommand(sel, true), expectedLines, expectedSelection); + testCommand(lines, mode.languageId, selection, (sel) => new BlockCommentCommand(sel, true, new TestLanguageConfigurationService()), expectedLines, expectedSelection); mode.dispose(); } @@ -475,7 +476,7 @@ suite('Editor Contrib - Block Comment Command', () => { test('insertSpace false', () => { function testLineCommentCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { let mode = new CommentMode({ lineComment: '!@#', blockComment: ['<0', '0>'] }); - testCommand(lines, mode.languageId, selection, (sel) => new BlockCommentCommand(sel, false), expectedLines, expectedSelection); + testCommand(lines, mode.languageId, selection, (sel) => new BlockCommentCommand(sel, false, new TestLanguageConfigurationService()), expectedLines, expectedSelection); mode.dispose(); } @@ -494,7 +495,7 @@ suite('Editor Contrib - Block Comment Command', () => { test('insertSpace false does not remove space', () => { function testLineCommentCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { let mode = new CommentMode({ lineComment: '!@#', blockComment: ['<0', '0>'] }); - testCommand(lines, mode.languageId, selection, (sel) => new BlockCommentCommand(sel, false), expectedLines, expectedSelection); + testCommand(lines, mode.languageId, selection, (sel) => new BlockCommentCommand(sel, false, new TestLanguageConfigurationService()), expectedLines, expectedSelection); mode.dispose(); } diff --git a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts index 7a9187c8a69a0..da3cf6560aba0 100644 --- a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts @@ -18,6 +18,7 @@ import { testCommand } from 'vs/editor/test/browser/testCommand'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CommentMode } from 'vs/editor/test/common/commentMode'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; function createTestCommandHelper(commentsConfig: CommentRule, commandFactory: (selection: Selection) => ICommand): (lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection) => void { return (lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection) => { @@ -32,12 +33,12 @@ suite('Editor Contrib - Line Comment Command', () => { const testLineCommentCommand = createTestCommandHelper( { lineComment: '!@#', blockComment: [''] }, - (sel) => new LineCommentCommand(sel, 4, Type.Toggle, true, true) + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.Toggle, true, true) ); const testAddLineCommentCommand = createTestCommandHelper( { lineComment: '!@#', blockComment: [''] }, - (sel) => new LineCommentCommand(sel, 4, Type.ForceAdd, true, true) + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.ForceAdd, true, true) ); test('comment single line', function () { @@ -58,7 +59,7 @@ suite('Editor Contrib - Line Comment Command', () => { test('case insensitive', function () { const testLineCommentCommand = createTestCommandHelper( { lineComment: 'rem' }, - (sel) => new LineCommentCommand(sel, 4, Type.Toggle, true, true) + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.Toggle, true, true) ); testLineCommentCommand( @@ -101,7 +102,7 @@ suite('Editor Contrib - Line Comment Command', () => { ' ', ' c', '\t\td' - ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false); + ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, new TestLanguageConfigurationService()); if (!r.supported) { throw new Error(`unexpected`); } @@ -132,7 +133,7 @@ suite('Editor Contrib - Line Comment Command', () => { ' rem ', ' !@# c', '\t\t!@#d' - ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false); + ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, new TestLanguageConfigurationService()); if (!r.supported) { throw new Error(`unexpected`); } @@ -641,7 +642,7 @@ suite('Editor Contrib - Line Comment Command', () => { test('insertSpace false', () => { const testLineCommentCommand = createTestCommandHelper( { lineComment: '!@#' }, - (sel) => new LineCommentCommand(sel, 4, Type.Toggle, false, true) + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.Toggle, false, true) ); testLineCommentCommand( @@ -659,7 +660,7 @@ suite('Editor Contrib - Line Comment Command', () => { test('insertSpace false does not remove space', () => { const testLineCommentCommand = createTestCommandHelper( { lineComment: '!@#' }, - (sel) => new LineCommentCommand(sel, 4, Type.Toggle, false, true) + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.Toggle, false, true) ); testLineCommentCommand( @@ -678,7 +679,7 @@ suite('Editor Contrib - Line Comment Command', () => { const testLineCommentCommand = createTestCommandHelper( { lineComment: '!@#', blockComment: [''] }, - (sel) => new LineCommentCommand(sel, 4, Type.Toggle, true, false) + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.Toggle, true, false) ); test('does not ignore whitespace lines', () => { @@ -770,7 +771,7 @@ suite('Editor Contrib - Line Comment As Block Comment', () => { const testLineCommentCommand = createTestCommandHelper( { lineComment: '', blockComment: ['(', ')'] }, - (sel) => new LineCommentCommand(sel, 4, Type.Toggle, true, true) + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.Toggle, true, true) ); test('fall back to block comment command', function () { @@ -881,7 +882,7 @@ suite('Editor Contrib - Line Comment As Block Comment 2', () => { const testLineCommentCommand = createTestCommandHelper( { lineComment: null, blockComment: [''] }, - (sel) => new LineCommentCommand(sel, 4, Type.Toggle, true, true) + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.Toggle, true, true) ); test('no selection => uses indentation', function () { @@ -1130,7 +1131,7 @@ suite('Editor Contrib - Line Comment in mixed modes', () => { lines, OUTER_LANGUAGE_ID, selection, - (sel) => new LineCommentCommand(sel, 4, Type.Toggle, true, true), + (sel) => new LineCommentCommand(new TestLanguageConfigurationService(), sel, 4, Type.Toggle, true, true), expectedLines, expectedSelection, true, diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 67b387117e676..6244c54aca195 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -23,7 +23,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { FoldingRangeKind, FoldingRangeProviderRegistry } from 'vs/editor/common/modes'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { CollapseMemento, FoldingModel, getNextFoldLine, getParentFoldLine as getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from 'vs/editor/contrib/folding/foldingModel'; import { HiddenRangeModel } from 'vs/editor/contrib/folding/hiddenRangeModel'; import { IndentRangeProvider } from 'vs/editor/contrib/folding/indentRangeProvider'; @@ -91,7 +91,8 @@ export class FoldingController extends Disposable implements IEditorContribution constructor( editor: ICodeEditor, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService, ) { super(); this.editor = editor; @@ -266,7 +267,7 @@ export class FoldingController extends Disposable implements IEditorContribution if (this.rangeProvider) { return this.rangeProvider; } - this.rangeProvider = new IndentRangeProvider(editorModel); // fallback + this.rangeProvider = new IndentRangeProvider(editorModel, this.languageConfigurationService); // fallback if (this._useFoldingProviders && this.foldingModel) { @@ -508,7 +509,7 @@ export class FoldingController extends Disposable implements IEditorContribution abstract class FoldingAction extends EditorAction { - abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T): void; + abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T, languageConfigurationService: ILanguageConfigurationService): void; public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise { const foldingController = FoldingController.get(editor); @@ -520,7 +521,7 @@ abstract class FoldingAction extends EditorAction { this.reportTelemetry(accessor, editor); return foldingModelPromise.then(foldingModel => { if (foldingModel) { - this.invoke(foldingController, foldingModel, editor, args); + this.invoke(foldingController, foldingModel, editor, args, accessor.get(ILanguageConfigurationService)); const selection = editor.getSelection(); if (selection) { foldingController.reveal(selection.getStartPosition()); @@ -789,7 +790,7 @@ class FoldAllBlockCommentsAction extends FoldingAction { }); } - invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void { + invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void { if (foldingModel.regions.hasTypes()) { setCollapseStateForType(foldingModel, FoldingRangeKind.Comment.value, true); } else { @@ -797,7 +798,7 @@ class FoldAllBlockCommentsAction extends FoldingAction { if (!editorModel) { return; } - const comments = LanguageConfigurationRegistry.getComments(editorModel.getLanguageId()); + const comments = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).comments; if (comments && comments.blockCommentStartToken) { let regExp = new RegExp('^\\s*' + escapeRegExpCharacters(comments.blockCommentStartToken)); setCollapseStateForMatchingLines(foldingModel, regExp, true); @@ -822,7 +823,7 @@ class FoldAllRegionsAction extends FoldingAction { }); } - invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void { + invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void { if (foldingModel.regions.hasTypes()) { setCollapseStateForType(foldingModel, FoldingRangeKind.Region.value, true); } else { @@ -830,7 +831,7 @@ class FoldAllRegionsAction extends FoldingAction { if (!editorModel) { return; } - const foldingRules = LanguageConfigurationRegistry.getFoldingRules(editorModel.getLanguageId()); + const foldingRules = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).foldingRules; if (foldingRules && foldingRules.markers && foldingRules.markers.start) { let regExp = new RegExp(foldingRules.markers.start); setCollapseStateForMatchingLines(foldingModel, regExp, true); @@ -855,7 +856,7 @@ class UnfoldAllRegionsAction extends FoldingAction { }); } - invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void { + invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void { if (foldingModel.regions.hasTypes()) { setCollapseStateForType(foldingModel, FoldingRangeKind.Region.value, false); } else { @@ -863,7 +864,7 @@ class UnfoldAllRegionsAction extends FoldingAction { if (!editorModel) { return; } - const foldingRules = LanguageConfigurationRegistry.getFoldingRules(editorModel.getLanguageId()); + const foldingRules = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).foldingRules; if (foldingRules && foldingRules.markers && foldingRules.markers.start) { let regExp = new RegExp(foldingRules.markers.start); setCollapseStateForMatchingLines(foldingModel, regExp, false); diff --git a/src/vs/editor/contrib/folding/indentRangeProvider.ts b/src/vs/editor/contrib/folding/indentRangeProvider.ts index 59178be94dd6f..dd14d1f5ff516 100644 --- a/src/vs/editor/contrib/folding/indentRangeProvider.ts +++ b/src/vs/editor/contrib/folding/indentRangeProvider.ts @@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModel } from 'vs/editor/common/model'; import { computeIndentLevel } from 'vs/editor/common/model/utils'; import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { FoldingRegions, MAX_LINE_NUMBER } from 'vs/editor/contrib/folding/foldingRanges'; import { RangeProvider } from './folding'; @@ -18,14 +18,15 @@ export const ID_INDENT_PROVIDER = 'indent'; export class IndentRangeProvider implements RangeProvider { readonly id = ID_INDENT_PROVIDER; - constructor(private readonly editorModel: ITextModel) { - } + constructor( + private readonly editorModel: ITextModel, + private readonly languageConfigurationService: ILanguageConfigurationService + ) { } - dispose() { - } + dispose() { } compute(cancelationToken: CancellationToken): Promise { - let foldingRules = LanguageConfigurationRegistry.getFoldingRules(this.editorModel.getLanguageId()); + let foldingRules = this.languageConfigurationService.getLanguageConfiguration(this.editorModel.getLanguageId()).foldingRules; let offSide = foldingRules && !!foldingRules.offSide; let markers = foldingRules && foldingRules.markers; return Promise.resolve(computeRanges(this.editorModel, offSide, markers)); diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 4f8a8890a7069..b0ecc91200e3a 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -17,20 +17,20 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { StandardTokenType, TextEdit } from 'vs/editor/common/modes'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules'; import { IModelService } from 'vs/editor/common/services/modelService'; import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; import * as nls from 'vs/nls'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -export function getReindentEditOperations(model: ITextModel, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): IIdentifiedSingleEditOperation[] { +export function getReindentEditOperations(model: ITextModel, languageConfigurationService: ILanguageConfigurationService, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): IIdentifiedSingleEditOperation[] { if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { // Model is empty return []; } - const indentationRules = LanguageConfigurationRegistry.getIndentationRules(model.getLanguageId()); + const indentationRules = languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).indentationRules; if (!indentationRules) { return []; } @@ -314,7 +314,7 @@ export class ReindentLinesAction extends EditorAction { if (!model) { return; } - let edits = getReindentEditOperations(model, 1, model.getLineCount()); + let edits = getReindentEditOperations(model, accessor.get(ILanguageConfigurationService), 1, model.getLineCount()); if (edits.length > 0) { editor.pushUndoStop(); editor.executeEdits(this.id, edits); @@ -362,7 +362,7 @@ export class ReindentSelectedLinesAction extends EditorAction { startLineNumber--; } - let editOperations = getReindentEditOperations(model, startLineNumber, endLineNumber); + let editOperations = getReindentEditOperations(model, accessor.get(ILanguageConfigurationService), startLineNumber, endLineNumber); edits.push(...editOperations); } diff --git a/src/vs/editor/contrib/linkedEditing/linkedEditing.ts b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts index 37c47c71cf620..b3ada5326db02 100644 --- a/src/vs/editor/contrib/linkedEditing/linkedEditing.ts +++ b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts @@ -8,6 +8,7 @@ import { CancelablePromise, createCancelablePromise, Delayer, first } from 'vs/b import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { isPromiseCanceledError, onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; +import { Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; @@ -23,7 +24,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IIdentifiedSingleEditOperation, IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { LinkedEditingRangeProviderRegistry, LinkedEditingRanges } from 'vs/editor/common/modes'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import * as nls from 'vs/nls'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -71,7 +72,8 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont constructor( editor: ICodeEditor, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService, ) { super(); this._editor = editor; @@ -120,10 +122,14 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont return; } - this._languageWordPattern = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageId()); - this._localToDispose.add(model.onDidChangeLanguageConfiguration(() => { - this._languageWordPattern = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageId()); - })); + this._localToDispose.add( + Event.runAndSubscribe( + model.onDidChangeLanguageConfiguration, + () => { + this._languageWordPattern = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); + } + ) + ); const rangeUpdateScheduler = new Delayer(this._debounceDuration); const triggerRangeUpdate = () => { @@ -450,3 +456,4 @@ registerModelAndPositionCommand('_executeLinkedEditingProvider', (model, positio registerEditorContribution(LinkedEditingContribution.ID, LinkedEditingContribution); registerEditorAction(LinkedEditingAction); + diff --git a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index 8c7d37745dc2d..e2b52f71016de 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -9,13 +9,15 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { deserializePipePositions, serializePipePositions, testRepeatedActionAndExtractPositions } from 'vs/editor/contrib/wordOperations/test/wordTestUtils'; import { CursorWordAccessibilityLeft, CursorWordAccessibilityLeftSelect, CursorWordAccessibilityRight, CursorWordAccessibilityRightSelect, CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorWordEndRightSelect, CursorWordLeft, CursorWordLeftSelect, CursorWordRight, CursorWordRightSelect, CursorWordStartLeft, CursorWordStartLeftSelect, CursorWordStartRight, CursorWordStartRightSelect, DeleteInsideWord, DeleteWordEndLeft, DeleteWordEndRight, DeleteWordLeft, DeleteWordRight, DeleteWordStartLeft, DeleteWordStartRight } from 'vs/editor/contrib/wordOperations/wordOperations'; +import { StaticServiceAccessor } from 'vs/editor/contrib/wordPartOperations/test/utils'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; suite('WordOperations', () => { @@ -43,8 +45,13 @@ suite('WordOperations', () => { const _deleteWordEndRight = new DeleteWordEndRight(); const _deleteInsideWord = new DeleteInsideWord(); + const serviceAccessor = new StaticServiceAccessor().withService( + ILanguageConfigurationService, + new TestLanguageConfigurationService() + ); + function runEditorCommand(editor: ICodeEditor, command: EditorCommand): void { - command.runEditorCommand(null, editor, null); + command.runEditorCommand(serviceAccessor, editor, null); } function cursorWordLeft(editor: ICodeEditor, inSelectionMode: boolean = false): void { runEditorCommand(editor, inSelectionMode ? _cursorWordLeftSelect : _cursorWordLeft); diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index 96fd4da7063b6..d514b0efa19b1 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -18,7 +18,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import * as nls from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -339,7 +339,8 @@ export abstract class DeleteWordCommand extends EditorCommand { const selections = editor.getSelections(); const autoClosingBrackets = editor.getOption(EditorOption.autoClosingBrackets); const autoClosingQuotes = editor.getOption(EditorOption.autoClosingQuotes); - const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(model.getLanguageId()); + const languageConfigurationService = accessor.get(ILanguageConfigurationService); + const autoClosingPairs = languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getAutoClosingPairs(); const viewModel = editor._getViewModel(); const commands = selections.map((sel) => { diff --git a/src/vs/editor/contrib/wordPartOperations/test/utils.ts b/src/vs/editor/contrib/wordPartOperations/test/utils.ts new file mode 100644 index 0000000000000..7610bd1893779 --- /dev/null +++ b/src/vs/editor/contrib/wordPartOperations/test/utils.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ServiceIdentifier, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; + +export class StaticServiceAccessor implements ServicesAccessor { + private services = new Map, any>(); + + public withService(id: ServiceIdentifier, service: T): this { + this.services.set(id, service); + return this; + } + + public get(id: ServiceIdentifier): T { + const value = this.services.get(id); + if (!value) { + throw new Error('Service does not exist'); + } + return value; + } +} diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts index 446711f2a5e7b..51503a7ca711d 100644 --- a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts +++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts @@ -7,8 +7,11 @@ import * as assert from 'assert'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { deserializePipePositions, serializePipePositions, testRepeatedActionAndExtractPositions } from 'vs/editor/contrib/wordOperations/test/wordTestUtils'; +import { StaticServiceAccessor } from 'vs/editor/contrib/wordPartOperations/test/utils'; import { CursorWordPartLeft, CursorWordPartLeftSelect, CursorWordPartRight, CursorWordPartRightSelect, DeleteWordPartLeft, DeleteWordPartRight } from 'vs/editor/contrib/wordPartOperations/wordPartOperations'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; suite('WordPartOperations', () => { const _deleteWordPartLeft = new DeleteWordPartLeft(); @@ -18,8 +21,13 @@ suite('WordPartOperations', () => { const _cursorWordPartRight = new CursorWordPartRight(); const _cursorWordPartRightSelect = new CursorWordPartRightSelect(); + const serviceAccessor = new StaticServiceAccessor().withService( + ILanguageConfigurationService, + new TestLanguageConfigurationService() + ); + function runEditorCommand(editor: ICodeEditor, command: EditorCommand): void { - command.runEditorCommand(null, editor, null); + command.runEditorCommand(serviceAccessor, editor, null); } function cursorWordPartLeft(editor: ICodeEditor, inSelectionmode: boolean = false): void { runEditorCommand(editor, inSelectionmode ? _cursorWordPartLeftSelect : _cursorWordPartLeft); diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 4d032b5b3de97..93118f64abffa 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -16,7 +16,7 @@ import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { isPatternInWord } from 'vs/base/common/filters'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; export class SnippetCompletion implements CompletionItem { @@ -58,7 +58,8 @@ export class SnippetCompletionProvider implements CompletionItemProvider { constructor( @IModeService private readonly _modeService: IModeService, - @ISnippetsService private readonly _snippets: ISnippetsService + @ISnippetsService private readonly _snippets: ISnippetsService, + @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService ) { // } @@ -96,7 +97,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider { // First check if there is anything to the right of the cursor if (columnOffset < lineContentLow.length) { - const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageId); + const autoClosingPairs = this._languageConfigurationService.getLanguageConfiguration(languageId).getAutoClosingPairs(); const standardAutoClosingPairConditionals = autoClosingPairs.autoClosingPairsCloseSingleChar.get(lineContentLow[columnOffset]); // If the character to the right of the cursor is a closing character of an autoclosing pair if (standardAutoClosingPairConditionals?.some(p => diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 2570add2d0e74..526973df0a7e0 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -29,6 +29,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { isStringArray } from 'vs/base/common/types'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; namespace snippetExt { @@ -191,7 +192,7 @@ class SnippetsService implements ISnippetsService { this._initWorkspaceSnippets(); }))); - setSnippetSuggestSupport(new SnippetCompletionProvider(this._modeService, this)); + setSnippetSuggestSupport(new SnippetCompletionProvider(this._modeService, this, new TestLanguageConfigurationService())); this._enablement = instantiationService.createInstance(SnippetEnablement); } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 739ea1646e787..f2604ce9abbde 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -14,6 +14,7 @@ import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/sn import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { CompletionContext, CompletionItemLabel, CompletionItemRanges, CompletionTriggerKind } from 'vs/editor/common/modes'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; class SimpleSnippetService implements ISnippetsService { declare readonly _serviceBrand: undefined; @@ -82,7 +83,7 @@ suite('SnippetsService', function () { test('snippet completions - simple', function () { - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -93,7 +94,7 @@ suite('SnippetsService', function () { test('snippet completions - simple 2', function () { - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('hello ', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 6), context)!.then(result => { @@ -104,7 +105,7 @@ suite('SnippetsService', function () { test('snippet completions - with prefix', function () { - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('bar', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 4), context)!.then(result => { @@ -139,7 +140,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('bar-bar', undefined, 'fooLang')); await provider.provideCompletionItems(model, new Position(1, 3), context)!.then(result => { @@ -209,7 +210,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('\t { @@ -244,7 +245,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('\n\t\n>/head>', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -274,7 +275,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -301,7 +302,7 @@ suite('SnippetsService', function () { '', SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('p-', undefined, 'fooLang')); @@ -326,7 +327,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; @@ -345,7 +346,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel(':', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; @@ -364,7 +365,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('template', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; @@ -387,7 +388,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; @@ -410,7 +411,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('.🐷-a-b', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 8), context)!; @@ -429,7 +430,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('a ', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -456,7 +457,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel(' <', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -486,7 +487,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('not wordFoo bar', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -528,7 +529,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('filler e KEEP ng filler', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; @@ -559,7 +560,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('[psc]', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 5), context)!; @@ -584,7 +585,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel(' ci', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 4), context)!; @@ -605,7 +606,7 @@ suite('SnippetsService', function () { // new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User) ]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('\'\'', undefined, 'fooLang'); let result = await provider.provideCompletionItems( @@ -627,7 +628,7 @@ suite('SnippetsService', function () { new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User) ]); - const provider = new SnippetCompletionProvider(modeService, snippetService); + const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('\'\'', undefined, 'fooLang'); From 1ffb4149d78dfb0247d41edad85969989a561581 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 7 Dec 2021 17:40:00 +0100 Subject: [PATCH 0363/2210] Fixes use of accessor after await. --- src/vs/editor/contrib/folding/folding.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 6244c54aca195..8c671287ef6d8 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -512,6 +512,7 @@ abstract class FoldingAction extends EditorAction { abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T, languageConfigurationService: ILanguageConfigurationService): void; public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise { + const languageConfigurationService = accessor.get(ILanguageConfigurationService); const foldingController = FoldingController.get(editor); if (!foldingController) { return; @@ -521,7 +522,7 @@ abstract class FoldingAction extends EditorAction { this.reportTelemetry(accessor, editor); return foldingModelPromise.then(foldingModel => { if (foldingModel) { - this.invoke(foldingController, foldingModel, editor, args, accessor.get(ILanguageConfigurationService)); + this.invoke(foldingController, foldingModel, editor, args, languageConfigurationService); const selection = editor.getSelection(); if (selection) { foldingController.reveal(selection.getStartPosition()); From b2e10fed3fe90f014f0de12474c0cb3348b9d275 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 7 Dec 2021 08:42:14 -0800 Subject: [PATCH 0364/2210] Implement round-robin classifier Fixes #136688 --- .github/workflows/deep-classifier-runner.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deep-classifier-runner.yml b/.github/workflows/deep-classifier-runner.yml index 19a8a705971ab..be5defca8b551 100644 --- a/.github/workflows/deep-classifier-runner.yml +++ b/.github/workflows/deep-classifier-runner.yml @@ -47,4 +47,5 @@ jobs: configPath: classifier allowLabels: "needs more info|new release" appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} + manifestDbConnectionString: ${{secrets.MANIFEST_DB_CONNECTION_STRING}} token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} From 6ee5fe3e9d98e10551f6a52da6e1777226d0a3df Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 7 Dec 2021 08:42:46 -0800 Subject: [PATCH 0365/2210] Pull in req. packages --- .github/workflows/deep-classifier-runner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deep-classifier-runner.yml b/.github/workflows/deep-classifier-runner.yml index be5defca8b551..ab0c6bd93ee51 100644 --- a/.github/workflows/deep-classifier-runner.yml +++ b/.github/workflows/deep-classifier-runner.yml @@ -19,7 +19,7 @@ jobs: run: npm install --production --prefix ./actions - name: Install Additional Dependencies # Pulls in a bunch of other packages that arent needed for the rest of the actions - run: npm install @azure/storage-blob@12.1.1 + run: npm install @azure/storage-blob@12.1.1 mongodb@2.2.31 - name: "Run Classifier: Scraper" uses: ./actions/classifier-deep/apply/fetch-sources with: From 51d7dd1a7319cca932d2db8bb6560e2fc8dc3c03 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Dec 2021 17:43:38 +0100 Subject: [PATCH 0366/2210] Selective task provider activation using a new `onTaskType:` activation event (#137662) --- extensions/grunt/package.json | 2 +- extensions/gulp/package.json | 2 +- extensions/jake/package.json | 2 +- extensions/npm/package.json | 2 +- .../typescript-language-features/package.json | 2 +- .../tasks/browser/abstractTaskService.ts | 165 ++++++++++++++---- .../contrib/tasks/browser/tasksQuickAccess.ts | 7 - .../extensions/common/extensionsRegistry.ts | 5 + 8 files changed, 139 insertions(+), 48 deletions(-) diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index c784b1ed83a11..7e5c605ec00ff 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -24,7 +24,7 @@ }, "main": "./out/main", "activationEvents": [ - "onCommand:workbench.action.tasks.runTask" + "onTaskType:grunt" ], "capabilities": { "virtualWorkspaces": false, diff --git a/extensions/gulp/package.json b/extensions/gulp/package.json index 21800e96cfb60..995da7fbe5d3a 100644 --- a/extensions/gulp/package.json +++ b/extensions/gulp/package.json @@ -24,7 +24,7 @@ }, "main": "./out/main", "activationEvents": [ - "onCommand:workbench.action.tasks.runTask" + "onTaskType:gulp" ], "capabilities": { "virtualWorkspaces": false, diff --git a/extensions/jake/package.json b/extensions/jake/package.json index 244d12d96e56f..4bb8793f3ee9a 100644 --- a/extensions/jake/package.json +++ b/extensions/jake/package.json @@ -24,7 +24,7 @@ }, "main": "./out/main", "activationEvents": [ - "onCommand:workbench.action.tasks.runTask" + "onTaskType:jake" ], "capabilities": { "virtualWorkspaces": false, diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 6aa1de094b8b3..8c4bc7ba0717d 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -37,7 +37,7 @@ "main": "./out/npmMain", "browser": "./dist/browser/npmBrowserMain", "activationEvents": [ - "onCommand:workbench.action.tasks.runTask", + "onTaskType:npm", "onCommand:npm.runScriptFromFolder", "onLanguage:json", "workspaceContains:package.json", diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 6651308b4247d..b5e11e8892abe 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -63,7 +63,7 @@ "onCommand:javascript.goToProjectConfig", "onCommand:typescript.goToProjectConfig", "onCommand:typescript.openTsServerLog", - "onCommand:workbench.action.tasks.runTask", + "onTaskType:typescript", "onCommand:_typescript.configurePlugin", "onCommand:_typescript.learnMoreAboutRefactorings", "onCommand:typescript.fileReferences", diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 2b3f346c6d861..c739e6f6c9510 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -150,7 +150,7 @@ class TaskMap { this._store.forEach(callback); } - private getKey(workspaceFolder: IWorkspace | IWorkspaceFolder | string): string { + public static getKey(workspaceFolder: IWorkspace | IWorkspaceFolder | string): string { let key: string | undefined; if (Types.isString(workspaceFolder)) { key = workspaceFolder; @@ -162,7 +162,7 @@ class TaskMap { } public get(workspaceFolder: IWorkspace | IWorkspaceFolder | string): Task[] { - const key = this.getKey(workspaceFolder); + const key = TaskMap.getKey(workspaceFolder); let result: Task[] | undefined = this._store.get(key); if (!result) { result = []; @@ -172,7 +172,7 @@ class TaskMap { } public add(workspaceFolder: IWorkspace | IWorkspaceFolder | string, ...task: Task[]): void { - const key = this.getKey(workspaceFolder); + const key = TaskMap.getKey(workspaceFolder); let values = this._store.get(key); if (!values) { values = []; @@ -493,6 +493,31 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._showIgnoreMessage; } + private _getActivationEvents(type: string | undefined): string[] { + const result: string[] = []; + result.push('onCommand:workbench.action.tasks.runTask'); + if (type) { + // send a specific activation event for this task type + result.push(`onTaskType:${type}`); + } else { + // send activation events for all task types + for (const definition of TaskDefinitionRegistry.all()) { + result.push(`onTaskType:${definition.taskType}`); + } + } + return result; + } + + private async _activateTaskProviders(type: string | undefined): Promise { + // We need to first wait for extensions to be registered because we might read + // the `TaskDefinitionRegistry` in case `type` is `undefined` + await this.extensionService.whenInstalledExtensionsRegistered(); + + await Promise.all( + this._getActivationEvents(type).map(activationEvent => this.extensionService.activateByEvent(activationEvent)) + ); + } + private updateSetup(setup?: [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined]): void { if (!setup) { setup = this.computeWorkspaceFolderSetup(); @@ -575,6 +600,44 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._taskSystem.customExecutionComplete(task, result); } + /** + * Get a subset of workspace tasks that match a certain predicate. + */ + private async _findWorkspaceTasks(predicate: (task: ConfiguringTask | Task, workspaceFolder: IWorkspaceFolder) => boolean): Promise<(ConfiguringTask | Task)[]> { + const result: (ConfiguringTask | Task)[] = []; + + const tasks = await this.getWorkspaceTasks(); + for (const [, workspaceTasks] of tasks) { + if (workspaceTasks.configurations) { + for (const taskName in workspaceTasks.configurations.byIdentifier) { + const task = workspaceTasks.configurations.byIdentifier[taskName]; + if (predicate(task, workspaceTasks.workspaceFolder)) { + result.push(task); + } + } + } + if (workspaceTasks.set) { + for (const task of workspaceTasks.set.tasks) { + if (predicate(task, workspaceTasks.workspaceFolder)) { + result.push(task); + } + } + } + } + + return result; + } + + private async _findWorkspaceTasksInGroup(group: TaskGroup, isDefault: boolean): Promise<(ConfiguringTask | Task)[]> { + return this._findWorkspaceTasks((task) => { + const taskGroup = task.configurationProperties.group; + if (taskGroup && typeof taskGroup !== 'string') { + return (taskGroup._id === group._id && (!isDefault || !!taskGroup.isDefault)); + } + return false; + }); + } + public async getTask(folder: IWorkspace | IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): Promise { if (!(await this.trust())) { return; @@ -590,6 +653,28 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (key === undefined) { return Promise.resolve(undefined); } + + // Try to find the task in the workspace + const requestedFolder = TaskMap.getKey(folder); + const matchedTasks = await this._findWorkspaceTasks((task, workspaceFolder) => { + const taskFolder = TaskMap.getKey(workspaceFolder); + if (taskFolder !== requestedFolder || taskFolder !== USER_TASKS_GROUP_KEY) { + return false; + } + return task.matches(key, compareId); + }); + matchedTasks.sort(task => task._source.kind === TaskSourceKind.Extension ? 1 : -1); + if (matchedTasks.length > 0) { + // Nice, we found a configured task! + const task = matchedTasks[0]; + if (ConfiguringTask.is(task)) { + return this.tryResolveTask(task); + } else { + return task; + } + } + + // We didn't find the task, so we need to ask all resolvers about it return this.getGroupedTasks().then((map) => { let values = map.get(folder); values = values.concat(map.get(USER_TASKS_GROUP_KEY)); @@ -606,7 +691,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!(await this.trust())) { return; } - await Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]); + await this._activateTaskProviders(configuringTask.type); let matchingProvider: ITaskProvider | undefined; let matchingProviderUnavailable: boolean = false; for (const [handle, provider] of this._providers) { @@ -690,10 +775,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer public taskTypes(): string[] { const types: string[] = []; if (this.isProvideTasksEnabled()) { - for (const [handle] of this._providers) { - const type = this._providerTypes.get(handle); - if (type && this.isTaskProviderEnabled(type)) { - types.push(type); + for (const definition of TaskDefinitionRegistry.all()) { + if (this.isTaskProviderEnabled(definition.taskType)) { + types.push(definition.taskType); } } } @@ -900,7 +984,28 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.openerService.open(URI.parse('https://code.visualstudio.com/docs/editor/tasks#_defining-a-problem-matcher')); } - private build(): Promise { + private async _findSingleWorkspaceTaskOfGroup(group: TaskGroup): Promise { + const tasksOfGroup = await this._findWorkspaceTasksInGroup(group, true); + if ((tasksOfGroup.length === 1) && (typeof tasksOfGroup[0].configurationProperties.group !== 'string') && tasksOfGroup[0].configurationProperties.group?.isDefault) { + let resolvedTask: Task | undefined; + if (ConfiguringTask.is(tasksOfGroup[0])) { + resolvedTask = await this.tryResolveTask(tasksOfGroup[0]); + } else { + resolvedTask = tasksOfGroup[0]; + } + if (resolvedTask) { + return this.run(resolvedTask, undefined, TaskRunSource.User); + } + } + return undefined; + } + + private async build(): Promise { + const tryBuildShortcut = await this._findSingleWorkspaceTaskOfGroup(TaskGroup.Build); + if (tryBuildShortcut) { + return tryBuildShortcut; + } + return this.getGroupedTasks().then((tasks) => { let runnable = this.createRunnableTask(tasks, TaskGroup.Build); if (!runnable || !runnable.task) { @@ -917,7 +1022,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - private runTest(): Promise { + private async runTest(): Promise { + const tryTestShortcut = await this._findSingleWorkspaceTaskOfGroup(TaskGroup.Test); + if (tryTestShortcut) { + return tryTestShortcut; + } + return this.getGroupedTasks().then((tasks) => { let runnable = this.createRunnableTask(tasks, TaskGroup.Test); if (!runnable || !runnable.task) { @@ -1683,7 +1793,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private getGroupedTasks(type?: string): Promise { const needsRecentTasksMigration = this.needsRecentTasksMigration(); - return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]).then(() => { + return this._activateTaskProviders(type).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; @@ -1717,12 +1827,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }; if (this.isProvideTasksEnabled() && (this.schemaVersion === JsonSchemaVersion.V2_0_0) && (this._providers.size > 0)) { + let foundAnyProviders = false; for (const [handle, provider] of this._providers) { const providerType = this._providerTypes.get(handle); if ((type === undefined) || (type === providerType)) { if (providerType && !this.isTaskProviderEnabled(providerType)) { continue; } + foundAnyProviders = true; counter++; provider.provideTasks(validTypes).then((taskSet: TaskSet) => { // Check that the tasks provided are of the correct type @@ -1739,6 +1851,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }, error); } } + if (!foundAnyProviders) { + resolve(result); + } } else { resolve(result); } @@ -2647,30 +2762,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer location: ProgressLocation.Window, title: nls.localize('TaskService.fetchingBuildTasks', 'Fetching build tasks...') }; - let promise = this.getWorkspaceTasks().then(tasks => { - const buildTasks: (ConfiguringTask | Task)[] = []; - for (const taskSource of tasks) { - for (const task in taskSource[1].configurations?.byIdentifier) { - if (taskSource[1].configurations) { - const taskGroup: TaskGroup = taskSource[1].configurations.byIdentifier[task].configurationProperties.group as TaskGroup; - - if (taskGroup && taskGroup._id === TaskGroup.Build._id && taskGroup.isDefault) { - buildTasks.push(taskSource[1].configurations.byIdentifier[task]); - } - } - } - if (taskSource[1].set) { - for (const task of taskSource[1].set?.tasks) { - const taskGroup: TaskGroup = task.configurationProperties.group as TaskGroup; - if (taskGroup && taskGroup._id === TaskGroup.Build._id && taskGroup.isDefault) { - buildTasks.push(task); - } - } - } - if (buildTasks.length > 0) { - break; - } - } + let promise = (async () => { + const buildTasks = await this._findWorkspaceTasksInGroup(TaskGroup.Build, false); async function runSingleBuildTask(task: Task | undefined, problemMatcherOptions: ProblemMatcherRunOptions | undefined, that: AbstractTaskService) { that.run(task, problemMatcherOptions, TaskRunSource.User).then(undefined, reason => { @@ -2720,7 +2813,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); }); }); - }); + })(); this.progressService.withProgress(options, () => promise); } diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index 3686ad45cbc88..24b9b2d97794d 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -22,8 +22,6 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider; - constructor( @IExtensionService extensionService: IExtensionService, @ITaskService private taskService: ITaskService, @@ -37,14 +35,9 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider> { - // always await extensions - await this.activationPromise; - if (token.isCancellationRequested) { return []; } diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index a32b31f59a319..7804f575dc3ff 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -288,6 +288,11 @@ export const schema: IJSONSchema = { description: nls.localize('vscode.extension.activationEvents.onStartupFinished', 'An activation event emitted after the start-up finished (after all `*` activated extensions have finished activating).'), body: 'onStartupFinished' }, + { + label: 'onTaskType', + description: nls.localize('vscode.extension.activationEvents.onTaskType', 'An activation event emitted whenever tasks of a certain type need to be listed or resolved.'), + body: 'onTaskType:${1:taskType}' + }, { label: 'onFileSystem', description: nls.localize('vscode.extension.activationEvents.onFileSystem', 'An activation event emitted whenever a file or folder is accessed with the given scheme.'), From 9ec4aed235245afa2bba1ca85a335094232a83b0 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 7 Dec 2021 11:50:27 -0500 Subject: [PATCH 0367/2210] Fix #135360 --- build/package.json | 2 +- build/yarn.lock | 77 ++++++++++++++++++++++++++++++++++--------- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 80 ++++++++++++++++++++++++++++++++++++--------- yarn.lock | 23 ++++--------- 6 files changed, 136 insertions(+), 50 deletions(-) diff --git a/build/package.json b/build/package.json index 7b1131e894c41..9363c77ca1922 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,7 @@ "@types/xml2js": "0.0.33", "@typescript-eslint/experimental-utils": "~2.13.0", "@typescript-eslint/parser": "^3.3.0", - "applicationinsights": "1.0.8", + "applicationinsights": "1.4.2", "byline": "^5.0.0", "colors": "^1.4.0", "commander": "^7.0.0", diff --git a/build/yarn.lock b/build/yarn.lock index bf575cab8d4ae..50d38113c6fd9 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -765,14 +765,15 @@ anymatch@^3.0.0: normalize-path "^3.0.0" picomatch "^2.0.4" -applicationinsights@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" - integrity sha512-KzOOGdphOS/lXWMFZe5440LUdFbrLpMvh2SaRxn7BmiI550KAoSb2gIhiq6kJZ9Ir3AxRRztjhzif+e5P5IXIg== +applicationinsights@1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.2.tgz#2f25f7a3f3e5bf0ab4486b63e42a48a9ec321d52" + integrity sha512-1wE37G9zEMZTsPJVQ8BDrQtsGgG3DGMActLHwPAF8TYHAXkfqqpeZYCH0XV4lUZ7H4MffRMwN2Ln2nEtUmT8HQ== dependencies: + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" diagnostic-channel "0.2.0" - diagnostic-channel-publishers "0.2.1" - zone.js "0.7.6" + diagnostic-channel-publishers "^0.3.3" argparse@^1.0.7: version "1.0.10" @@ -808,6 +809,21 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1012,6 +1028,15 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1081,6 +1106,14 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + core-js@^3.6.5: version "3.15.2" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" @@ -1188,10 +1221,10 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -diagnostic-channel-publishers@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" - integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM= +diagnostic-channel-publishers@^0.3.3: + version "0.3.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536" + integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ== diagnostic-channel@0.2.0: version "0.2.0" @@ -1264,6 +1297,13 @@ electron-osx-sign@^0.4.16: minimist "^1.2.0" plist "^3.0.1" +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + encodeurl@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2324,7 +2364,7 @@ semver@^5.1.0, semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@^5.6.0: +semver@^5.4.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -2360,6 +2400,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -2389,6 +2434,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= + stoppable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" @@ -2656,8 +2706,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zone.js@0.7.6: - version "0.7.6" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" - integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk= diff --git a/package.json b/package.json index 8d5adcaa7083d..2336171c6c2ef 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@vscode/sqlite3": "4.0.12", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", - "applicationinsights": "1.0.8", + "applicationinsights": "1.4.2", "graceful-fs": "4.2.8", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", diff --git a/remote/package.json b/remote/package.json index 7f0f62482783c..ff1a4ddde556e 100644 --- a/remote/package.json +++ b/remote/package.json @@ -6,7 +6,7 @@ "@microsoft/applicationinsights-web": "^2.6.4", "@parcel/watcher": "2.0.4", "@vscode/vscode-languagedetection": "1.0.21", - "applicationinsights": "1.0.8", + "applicationinsights": "1.4.2", "cookie": "^0.4.0", "graceful-fs": "4.2.8", "http-proxy-agent": "^2.1.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index 58db506c0ea54..9f7aa9369d4ad 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -127,14 +127,30 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -applicationinsights@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" - integrity sha512-KzOOGdphOS/lXWMFZe5440LUdFbrLpMvh2SaRxn7BmiI550KAoSb2gIhiq6kJZ9Ir3AxRRztjhzif+e5P5IXIg== +applicationinsights@1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.2.tgz#2f25f7a3f3e5bf0ab4486b63e42a48a9ec321d52" + integrity sha512-1wE37G9zEMZTsPJVQ8BDrQtsGgG3DGMActLHwPAF8TYHAXkfqqpeZYCH0XV4lUZ7H4MffRMwN2Ln2nEtUmT8HQ== dependencies: + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" diagnostic-channel "0.2.0" - diagnostic-channel-publishers "0.2.1" - zone.js "0.7.6" + diagnostic-channel-publishers "^0.3.3" + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" bindings@^1.5.0: version "1.5.0" @@ -148,6 +164,23 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + cookie@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" @@ -184,10 +217,10 @@ debug@^4.3.1: dependencies: ms "2.1.2" -diagnostic-channel-publishers@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" - integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM= +diagnostic-channel-publishers@^0.3.3: + version "0.3.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536" + integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ== diagnostic-channel@0.2.0: version "0.2.0" @@ -196,6 +229,13 @@ diagnostic-channel@0.2.0: dependencies: semver "^5.3.0" +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + es6-promise@^4.0.3: version "4.2.4" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" @@ -421,6 +461,16 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== +semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + smart-buffer@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" @@ -452,6 +502,11 @@ spdlog@^0.13.0: mkdirp "^0.5.5" nan "^2.14.0" +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -573,8 +628,3 @@ yazl@^2.4.3: integrity sha1-7CblzIfVYBud+EMtvdPNLlFzoHE= dependencies: buffer-crc32 "~0.2.3" - -zone.js@0.7.6: - version "0.7.6" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" - integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk= diff --git a/yarn.lock b/yarn.lock index 3a8a014bc6b19..8466cd0156920 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1487,14 +1487,15 @@ applicationinsights@*: diagnostic-channel "0.2.0" diagnostic-channel-publishers "^0.3.3" -applicationinsights@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" - integrity sha512-KzOOGdphOS/lXWMFZe5440LUdFbrLpMvh2SaRxn7BmiI550KAoSb2gIhiq6kJZ9Ir3AxRRztjhzif+e5P5IXIg== +applicationinsights@1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.2.tgz#2f25f7a3f3e5bf0ab4486b63e42a48a9ec321d52" + integrity sha512-1wE37G9zEMZTsPJVQ8BDrQtsGgG3DGMActLHwPAF8TYHAXkfqqpeZYCH0XV4lUZ7H4MffRMwN2Ln2nEtUmT8HQ== dependencies: + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" diagnostic-channel "0.2.0" - diagnostic-channel-publishers "0.2.1" - zone.js "0.7.6" + diagnostic-channel-publishers "^0.3.3" aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" @@ -3228,11 +3229,6 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== -diagnostic-channel-publishers@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" - integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM= - diagnostic-channel-publishers@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.3.tgz#376b7798f4fa90f37eb4f94d2caca611b0e9c330" @@ -11074,8 +11070,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zone.js@0.7.6: - version "0.7.6" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" - integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk= From f8cb7b25a0726595d1a905d71a0e439a74454ea6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Dec 2021 08:51:02 -0800 Subject: [PATCH 0368/2210] Make all TerminalConfigHelper unit tests sync Not sure why this would hang as the TestConfigurationService call just returns a resolved promise. Regardless, this should fix async timeout issues a the tests are now all sync. Fixes #138584 --- .../test/browser/terminalConfigHelper.test.ts | 248 ++++++++++-------- 1 file changed, 137 insertions(+), 111 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts index 20c3cc75d5ba2..e6530a5243529 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts @@ -22,71 +22,79 @@ suite('Workbench - TerminalConfigHelper', () => { fixture = document.body; }); - test('TerminalConfigHelper - getFont fontFamily', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); - await configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: 'bar' } }); + test('TerminalConfigHelper - getFont fontFamily', () => { + const configurationService = new TestConfigurationService({ + editor: { fontFamily: 'foo' }, + terminal: { integrated: { fontFamily: 'bar' } } + }); const configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontFamily, 'bar', 'terminal.integrated.fontFamily should be selected over editor.fontFamily'); }); - test('TerminalConfigHelper - getFont fontFamily (Linux Fedora)', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); - await configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); + test('TerminalConfigHelper - getFont fontFamily (Linux Fedora)', () => { + const configurationService = new TestConfigurationService({ + editor: { fontFamily: 'foo' }, + terminal: { integrated: { fontFamily: null } } + }); const configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.linuxDistro = LinuxDistro.Fedora; configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontFamily, '\'DejaVu Sans Mono\', monospace', 'Fedora should have its font overridden when terminal.integrated.fontFamily not set'); }); - test('TerminalConfigHelper - getFont fontFamily (Linux Ubuntu)', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); - await configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); + test('TerminalConfigHelper - getFont fontFamily (Linux Ubuntu)', () => { + const configurationService = new TestConfigurationService({ + editor: { fontFamily: 'foo' }, + terminal: { integrated: { fontFamily: null } } + }); const configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.linuxDistro = LinuxDistro.Ubuntu; configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontFamily, '\'Ubuntu Mono\', monospace', 'Ubuntu should have its font overridden when terminal.integrated.fontFamily not set'); }); - test('TerminalConfigHelper - getFont fontFamily (Linux Unknown)', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); - await configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); + test('TerminalConfigHelper - getFont fontFamily (Linux Unknown)', () => { + const configurationService = new TestConfigurationService({ + editor: { fontFamily: 'foo' }, + terminal: { integrated: { fontFamily: null } } + }); const configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontFamily, 'foo', 'editor.fontFamily should be the fallback when terminal.integrated.fontFamily not set'); }); - test('TerminalConfigHelper - getFont fontSize', async () => { - const configurationService = new TestConfigurationService(); - - await configurationService.setUserConfiguration('editor', { - fontFamily: 'foo', - fontSize: 9 - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: 'bar', - fontSize: 10 + test('TerminalConfigHelper - getFont fontSize 10', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'foo', + fontSize: 9 + }, + terminal: { + integrated: { + fontFamily: 'bar', + fontSize: 10 + } } }); - let configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); + const configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontSize, 10, 'terminal.integrated.fontSize should be selected over editor.fontSize'); + }); - await configurationService.setUserConfiguration('editor', { - fontFamily: 'foo' - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: null, - fontSize: 0 + test('TerminalConfigHelper - getFont fontSize 0', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'foo' + }, + terminal: { + integrated: { + fontFamily: null, + fontSize: 0 + } } }); - configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); + let configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.linuxDistro = LinuxDistro.Ubuntu; configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontSize, 8, 'The minimum terminal font size (with adjustment) should be used when terminal.integrated.fontSize less than it'); @@ -94,30 +102,38 @@ suite('Workbench - TerminalConfigHelper', () => { configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontSize, 6, 'The minimum terminal font size should be used when terminal.integrated.fontSize less than it'); + }); - await configurationService.setUserConfiguration('editor', { - fontFamily: 'foo' - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: 0, - fontSize: 1500 + test('TerminalConfigHelper - getFont fontSize 1500', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'foo' + }, + terminal: { + integrated: { + fontFamily: 0, + fontSize: 1500 + } } }); - configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); + const configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontSize, 100, 'The maximum terminal font size should be used when terminal.integrated.fontSize more than it'); + }); - await configurationService.setUserConfiguration('editor', { - fontFamily: 'foo' - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: 0, - fontSize: null + test('TerminalConfigHelper - getFont fontSize null', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'foo' + }, + terminal: { + integrated: { + fontFamily: 0, + fontSize: null + } } }); - configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); + let configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.linuxDistro = LinuxDistro.Ubuntu; configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize + 2, 'The default editor font size (with adjustment) should be used when terminal.integrated.fontSize is not set'); @@ -127,43 +143,48 @@ suite('Workbench - TerminalConfigHelper', () => { assert.strictEqual(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize, 'The default editor font size should be used when terminal.integrated.fontSize is not set'); }); - test('TerminalConfigHelper - getFont lineHeight', async () => { - const configurationService = new TestConfigurationService(); - - await configurationService.setUserConfiguration('editor', { - fontFamily: 'foo', - lineHeight: 1 - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: 0, - lineHeight: 2 + test('TerminalConfigHelper - getFont lineHeight 2', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'foo', + lineHeight: 1 + }, + terminal: { + integrated: { + fontFamily: 0, + lineHeight: 2 + } } }); let configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().lineHeight, 2, 'terminal.integrated.lineHeight should be selected over editor.lineHeight'); + }); - await configurationService.setUserConfiguration('editor', { - fontFamily: 'foo', - lineHeight: 1 - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: 0, - lineHeight: 0 + test('TerminalConfigHelper - getFont lineHeight 0', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'foo', + lineHeight: 1 + }, + terminal: { + integrated: { + fontFamily: 0, + lineHeight: 0 + } } }); - configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); + let configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); configHelper.panelContainer = fixture; assert.strictEqual(configHelper.getFont().lineHeight, 1, 'editor.lineHeight should be 1 when terminal.integrated.lineHeight not set'); }); - test('TerminalConfigHelper - isMonospace monospace', async function () { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: 'monospace' + test('TerminalConfigHelper - isMonospace monospace', () => { + const configurationService = new TestConfigurationService({ + terminal: { + integrated: { + fontFamily: 'monospace' + } } }); @@ -172,11 +193,12 @@ suite('Workbench - TerminalConfigHelper', () => { assert.strictEqual(configHelper.configFontIsMonospace(), true, 'monospace is monospaced'); }); - test('TerminalConfigHelper - isMonospace sans-serif', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: 'sans-serif' + test('TerminalConfigHelper - isMonospace sans-serif', () => { + const configurationService = new TestConfigurationService({ + terminal: { + integrated: { + fontFamily: 'sans-serif' + } } }); const configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); @@ -184,11 +206,12 @@ suite('Workbench - TerminalConfigHelper', () => { assert.strictEqual(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced'); }); - test('TerminalConfigHelper - isMonospace serif', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: 'serif' + test('TerminalConfigHelper - isMonospace serif', () => { + const configurationService = new TestConfigurationService({ + terminal: { + integrated: { + fontFamily: 'serif' + } } }); const configHelper = new TestTerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); @@ -196,14 +219,15 @@ suite('Workbench - TerminalConfigHelper', () => { assert.strictEqual(configHelper.configFontIsMonospace(), false, 'serif is not monospaced'); }); - test('TerminalConfigHelper - isMonospace monospace falls back to editor.fontFamily', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('editor', { - fontFamily: 'monospace' - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: null + test('TerminalConfigHelper - isMonospace monospace falls back to editor.fontFamily', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'monospace' + }, + terminal: { + integrated: { + fontFamily: null + } } }); @@ -212,14 +236,15 @@ suite('Workbench - TerminalConfigHelper', () => { assert.strictEqual(configHelper.configFontIsMonospace(), true, 'monospace is monospaced'); }); - test('TerminalConfigHelper - isMonospace sans-serif falls back to editor.fontFamily', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('editor', { - fontFamily: 'sans-serif' - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: null + test('TerminalConfigHelper - isMonospace sans-serif falls back to editor.fontFamily', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'sans-serif' + }, + terminal: { + integrated: { + fontFamily: null + } } }); @@ -228,14 +253,15 @@ suite('Workbench - TerminalConfigHelper', () => { assert.strictEqual(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced'); }); - test('TerminalConfigHelper - isMonospace serif falls back to editor.fontFamily', async () => { - const configurationService = new TestConfigurationService(); - await configurationService.setUserConfiguration('editor', { - fontFamily: 'serif' - }); - await configurationService.setUserConfiguration('terminal', { - integrated: { - fontFamily: null + test('TerminalConfigHelper - isMonospace serif falls back to editor.fontFamily', () => { + const configurationService = new TestConfigurationService({ + editor: { + fontFamily: 'serif' + }, + terminal: { + integrated: { + fontFamily: null + } } }); From 7af42b4d9bfdccfc4fee67f6e602e0656454e076 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 7 Dec 2021 17:54:12 +0100 Subject: [PATCH 0369/2210] Fixes #118365: Remove 'Droid Sans Fallback' from default Linux font family because it causes rendering problems for Korean glyphs --- src/vs/editor/common/config/editorOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 00802e5b30601..b3a054a2d9744 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4242,7 +4242,7 @@ class EditorWrappingInfoComputer extends ComputedEditorOption Date: Tue, 7 Dec 2021 17:58:15 +0100 Subject: [PATCH 0370/2210] Add test for Linux - Hangul --- .../browser/controller/textAreaInput.test.ts | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index 481fd687f6d47..e3619f5270cf1 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -255,7 +255,7 @@ suite('TextAreaInput', () => { value: text, selectionStart: selectionStart, selectionEnd: selectionEnd, - selectionDirection: (browser.isFirefox || OS === OperatingSystem.Windows) ? 'forward' : 'none' + selectionDirection: (browser.isFirefox || OS === OperatingSystem.Windows || OS === OperatingSystem.Linux) ? 'forward' : 'none' }; } @@ -1292,4 +1292,48 @@ suite('TextAreaInput', () => { assert.deepStrictEqual(actualResultingState, recorded.final); }); + test('Linux - Chrome - Korean', async () => { + // Linux, fcitx Hangul, Type 'rkr' and then click. + const recorded: IRecorded = { + env: { OS: OperatingSystem.Linux, browser: { isAndroid: false, isFirefox: false, isChrome: true, isSafari: false } }, + initial: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, + events: [ + { timeStamp: 0.00, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: '', ctrlKey: false, isComposing: false, key: 'Unidentified', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1.20, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionstart', data: '' }, + { timeStamp: 1.30, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'beforeinput', data: 'ㄱ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 1.40, state: { value: 'aaaa', selectionStart: 2, selectionEnd: 2, selectionDirection: 'forward' }, type: 'compositionupdate', data: 'ㄱ' }, + { timeStamp: 1.70, state: { value: 'aaㄱaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: 'ㄱ', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 104.50, state: { value: 'aaㄱaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'r', keyCode: 82, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 150.60, state: { value: 'aaㄱaa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: '', ctrlKey: false, isComposing: true, key: 'Unidentified', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 151.30, state: { value: 'aaㄱaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '가', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 151.40, state: { value: 'aaㄱaa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '가' }, + { timeStamp: 151.80, state: { value: 'aa가aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '가', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 248.50, state: { value: 'aa가aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyK', ctrlKey: false, isComposing: true, key: 'k', keyCode: 75, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 322.90, state: { value: 'aa가aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keydown', altKey: false, charCode: 0, code: '', ctrlKey: false, isComposing: true, key: 'Unidentified', keyCode: 229, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 323.70, state: { value: 'aa가aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'beforeinput', data: '각', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 323.90, state: { value: 'aa가aa', selectionStart: 2, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionupdate', data: '각' }, + { timeStamp: 324.10, state: { value: 'aa각aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'input', data: '각', inputType: 'insertCompositionText', isComposing: true }, + { timeStamp: 448.50, state: { value: 'aa각aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'keyup', altKey: false, charCode: 0, code: 'KeyR', ctrlKey: false, isComposing: true, key: 'r', keyCode: 82, location: 0, metaKey: false, repeat: false, shiftKey: false }, + { timeStamp: 1761.00, state: { value: 'aa각aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, type: 'compositionend', data: '각' } + ], + final: { value: 'aa각aa', selectionStart: 3, selectionEnd: 3, selectionDirection: 'forward' }, + }; + + const actualOutgoingEvents = await simulateInteraction(recorded); + assert.deepStrictEqual(actualOutgoingEvents, [ + { type: 'compositionStart', revealDeltaColumns: 0 }, + { type: 'type', text: 'ㄱ', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: 'ㄱ' }, + { type: 'type', text: '가', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '가' }, + { type: 'type', text: '각', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionUpdate', data: '각' }, + { type: 'type', text: '각', replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 }, + { type: 'compositionEnd' } + ]); + + const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents); + assert.deepStrictEqual(actualResultingState, recorded.final); + }); + }); From e424ed6a7ba008e075e54292e024ce0cef64d78b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Dec 2021 09:00:29 -0800 Subject: [PATCH 0371/2210] Disable TerminalCommandTracker Part of #138609 Part of #138273 --- .../terminal/test/browser/terminalCommandTracker.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts index 22f17831da174..722b1723f2d4f 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts @@ -20,7 +20,7 @@ async function writeP(terminal: TestTerminal, data: string): Promise { return new Promise(r => terminal.write(data, r)); } -suite('Workbench - TerminalCommandTracker', () => { +suite.skip('Workbench - TerminalCommandTracker', () => { let xterm: TestTerminal; let commandTracker: CommandTrackerAddon; From 4a959befed07f4c8f8314592556d83c651de0c28 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Dec 2021 09:09:51 -0800 Subject: [PATCH 0372/2210] Reject xterm writeP when it takes over 2 seconds Part of #138273 --- .../browser/terminalCommandTracker.test.ts | 66 +++++++++++-------- .../browser/xterm/lineDataEventAddon.test.ts | 10 ++- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts index 722b1723f2d4f..f475d6b4c8907 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts @@ -8,6 +8,7 @@ import { Terminal } from 'xterm'; import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { isWindows } from 'vs/base/common/platform'; import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; +import { timeout } from 'vs/base/common/async'; interface TestTerminal extends Terminal { _core: IXtermCore; @@ -17,14 +18,22 @@ const ROWS = 10; const COLS = 10; async function writeP(terminal: TestTerminal, data: string): Promise { - return new Promise(r => terminal.write(data, r)); + return new Promise((resolve, reject) => { + const failTimeout = timeout(2000); + failTimeout.then(() => reject('Writing to xterm is taking longer than 2 seconds')); + terminal.write(data, () => { + failTimeout.cancel(); + resolve(); + }); + }); } -suite.skip('Workbench - TerminalCommandTracker', () => { +suite.only('Workbench - TerminalCommandTracker', () => { let xterm: TestTerminal; let commandTracker: CommandTrackerAddon; setup(async () => { + console.log('setup 1'); xterm = (new Terminal({ cols: COLS, rows: ROWS @@ -55,6 +64,7 @@ suite.skip('Workbench - TerminalCommandTracker', () => { suite('Commands', () => { let container: HTMLElement; setup(() => { + console.log('setup 2'); (window).matchMedia = () => { return { addListener: () => { } }; }; @@ -92,31 +102,33 @@ suite.skip('Workbench - TerminalCommandTracker', () => { commandTracker.scrollToNextCommand(); assert.strictEqual(xterm.buffer.active.viewportY, 20); }); - test('should select to the next and previous commands', async () => { - await writeP(xterm, '\r0'); - await writeP(xterm, '\n\r1'); - await writeP(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm._core._onKey.fire({ key: '\x0d' }); // Mark line - assert.strictEqual(xterm.markers[0].line, 10); - await writeP(xterm, '\n\r2'); - await writeP(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm._core._onKey.fire({ key: '\x0d' }); // Mark line - assert.strictEqual(xterm.markers[1].line, 11); - await writeP(xterm, '\n\r3'); - - assert.strictEqual(xterm.buffer.active.baseY, 3); - assert.strictEqual(xterm.buffer.active.viewportY, 3); - - assert.strictEqual(xterm.getSelection(), ''); - commandTracker.selectToPreviousCommand(); - assert.strictEqual(xterm.getSelection(), '2'); - commandTracker.selectToPreviousCommand(); - assert.strictEqual(xterm.getSelection(), isWindows ? '1\r\n2' : '1\n2'); - commandTracker.selectToNextCommand(); - assert.strictEqual(xterm.getSelection(), '2'); - commandTracker.selectToNextCommand(); - assert.strictEqual(xterm.getSelection(), isWindows ? '\r\n' : '\n'); - }); + for (let i = 0; i < 100; i++) { + test('should select to the next and previous commands ' + i, async () => { + await writeP(xterm, '\r0'); + await writeP(xterm, '\n\r1'); + await writeP(xterm, '\x1b[3G'); // Move cursor to column 3 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line + assert.strictEqual(xterm.markers[0].line, 10); + await writeP(xterm, '\n\r2'); + await writeP(xterm, '\x1b[3G'); // Move cursor to column 3 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line + assert.strictEqual(xterm.markers[1].line, 11); + await writeP(xterm, '\n\r3'); + + assert.strictEqual(xterm.buffer.active.baseY, 3); + assert.strictEqual(xterm.buffer.active.viewportY, 3); + + assert.strictEqual(xterm.getSelection(), ''); + commandTracker.selectToPreviousCommand(); + assert.strictEqual(xterm.getSelection(), '2'); + commandTracker.selectToPreviousCommand(); + assert.strictEqual(xterm.getSelection(), isWindows ? '1\r\n2' : '1\n2'); + commandTracker.selectToNextCommand(); + assert.strictEqual(xterm.getSelection(), '2'); + commandTracker.selectToNextCommand(); + assert.strictEqual(xterm.getSelection(), isWindows ? '\r\n' : '\n'); + }); + } test('should select to the next and previous lines & commands', async () => { await writeP(xterm, '\r0'); await writeP(xterm, '\n\r1'); diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/lineDataEventAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/lineDataEventAddon.test.ts index adda5c0755d86..5f2bad7861293 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/lineDataEventAddon.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/lineDataEventAddon.test.ts @@ -7,9 +7,17 @@ import { Terminal } from 'xterm'; import { LineDataEventAddon } from 'vs/workbench/contrib/terminal/browser/xterm/lineDataEventAddon'; import { OperatingSystem } from 'vs/base/common/platform'; import { deepStrictEqual } from 'assert'; +import { timeout } from 'vs/base/common/async'; async function writeP(terminal: Terminal, data: string): Promise { - return new Promise(r => terminal.write(data, r)); + return new Promise((resolve, reject) => { + const failTimeout = timeout(2000); + failTimeout.then(() => reject('Writing to xterm is taking longer than 2 seconds')); + terminal.write(data, () => { + failTimeout.cancel(); + resolve(); + }); + }); } suite('LineDataEventAddon', () => { From edefc276b8e0adeeac548a8ada8b59ce8844a094 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Dec 2021 09:21:45 -0800 Subject: [PATCH 0373/2210] Remove only --- .../terminal/test/browser/terminalCommandTracker.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts index f475d6b4c8907..ff60bc14c9336 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts @@ -28,7 +28,7 @@ async function writeP(terminal: TestTerminal, data: string): Promise { }); } -suite.only('Workbench - TerminalCommandTracker', () => { +suite('Workbench - TerminalCommandTracker', () => { let xterm: TestTerminal; let commandTracker: CommandTrackerAddon; From a5c5b6a062ea5f115b1f30d83e302ae9b8369b43 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 7 Dec 2021 09:41:08 -0800 Subject: [PATCH 0374/2210] Strip out icons in the native hover. Fixes #2488 --- src/vs/base/browser/ui/iconLabel/iconLabelHover.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 137396ef97313..606aa31221092 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -10,13 +10,15 @@ import { IIconLabelMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabel import { TimeoutTimer } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; +import { stripIcons } from 'vs/base/common/iconLabels'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { isFunction, isString } from 'vs/base/common/types'; import { localize } from 'vs/nls'; export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void { if (isString(tooltip)) { - htmlElement.title = tooltip; + // Icons don't render in the native hover so we strip them out + htmlElement.title = stripIcons(tooltip); } else if (tooltip?.markdownNotSupportedFallback) { htmlElement.title = tooltip.markdownNotSupportedFallback; } else { From 8a135f734ea51f59b3c7351bafcdc558686db643 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 7 Dec 2021 11:17:55 -0800 Subject: [PATCH 0375/2210] fix #128406 --- .../workbench/contrib/terminal/browser/terminalInstance.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 623c78d0956ca..3b3e69ae00f67 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -990,7 +990,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } async sendPath(originalPath: string, addNewLine: boolean): Promise { - const preparedPath = await preparePathForShell(originalPath, this.shellLaunchConfig.executable, this.title, this.shellType, this._processManager.backend); + const preparedPath = await preparePathForShell(originalPath, this.shellLaunchConfig.executable, this.title, this.shellType, this._processManager.backend, this._processManager.os); return this.sendText(preparedPath, addNewLine); } @@ -2208,7 +2208,7 @@ export function parseExitResult( * @param backend The backend for the terminal. * @returns An escaped version of the path to be execuded in the terminal. */ -async function preparePathForShell(originalPath: string, executable: string | undefined, title: string, shellType: TerminalShellType, backend: ITerminalBackend | undefined): Promise { +async function preparePathForShell(originalPath: string, executable: string | undefined, title: string, shellType: TerminalShellType, backend: ITerminalBackend | undefined, os: OperatingSystem | undefined): Promise { return new Promise(c => { if (!executable) { c(originalPath); @@ -2235,7 +2235,7 @@ async function preparePathForShell(originalPath: string, executable: string | un } // TODO: This should use the process manager's OS, not the local OS - if (isWindows) { + if (os === OperatingSystem.Windows) { // 17063 is the build number where wsl path was introduced. // Update Windows uriPath to be executed in WSL. if (shellType !== undefined) { From f8455fc38e521dfff3f359a26e67ffb57004ca58 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 7 Dec 2021 11:58:07 -0800 Subject: [PATCH 0376/2210] update to windows latest in azure pipelines (#137691) * update to windows latest * use ExtractFile task instead of tar directly --- build/azure-pipelines/product-build.yml | 2 +- build/azure-pipelines/sdl-scan.yml | 2 +- build/azure-pipelines/win32/product-build-win32.yml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index c0b619cab095c..1977a7e2568d8 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -152,7 +152,7 @@ stages: dependsOn: - Compile pool: - vmImage: VS2017-Win2016 + vmImage: windows-latest jobs: - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - job: Windows diff --git a/build/azure-pipelines/sdl-scan.yml b/build/azure-pipelines/sdl-scan.yml index d1cd72b3d9544..d417a1c4ed6ac 100644 --- a/build/azure-pipelines/sdl-scan.yml +++ b/build/azure-pipelines/sdl-scan.yml @@ -35,7 +35,7 @@ stages: - stage: Windows condition: eq(variables.SCAN_WINDOWS, 'true') pool: - vmImage: VS2017-Win2016 + vmImage: windows-latest jobs: - job: WindowsJob timeoutInMinutes: 0 diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 9276bf15785d6..f3c5344595012 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -21,11 +21,11 @@ steps: path: $(Build.ArtifactStagingDirectory) displayName: Download compilation output - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { tar --force-local -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz } + - task: ExtractFiles@1 displayName: Extract compilation output + inputs: + archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/compilation.tar.gz' + cleanDestinationFolder: false - powershell: | . build/azure-pipelines/win32/exec.ps1 From 4024a1e84d21239bcb9dc12829bbe67668d1cb06 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 7 Dec 2021 12:00:36 -0800 Subject: [PATCH 0377/2210] Experiment with using message channel to exchange webview messages Currently all vscode <-> webview communication uses post message. This is a bit messy because we end up using a global `message` event listener on the VS Code side Instead, it makes more sense to use a private message channel for this communication except for the initilization --- .../contrib/webview/browser/pre/main.js | 24 ++++---- .../contrib/webview/browser/webviewElement.ts | 59 +++++++++++-------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index ed2e896233cc9..3a36f461de159 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -264,16 +264,14 @@ const workerReady = new Promise((resolve, reject) => { }); const hostMessaging = new class HostMessaging { + constructor() { + this.channel = new MessageChannel(); + /** @type {Map void>>} */ this.handlers = new Map(); - window.addEventListener('message', (e) => { - if (e.origin !== parentOrigin) { - console.log(`skipping webview message due to mismatched origins: ${e.origin} ${parentOrigin}`); - return; - } - + this.channel.port1.onmessage = (e) => { const channel = e.data.channel; const handlers = this.handlers.get(channel); if (handlers) { @@ -283,15 +281,16 @@ const hostMessaging = new class HostMessaging { } else { console.log('no handler for ', e); } - }); + }; } /** * @param {string} channel * @param {any} data + * @param {any} [transfer] */ - postMessage(channel, data) { - window.parent.postMessage({ target: ID, channel, data }, parentOrigin); + postMessage(channel, data, transfer) { + this.channel.port1.postMessage({ channel, data }, transfer); } /** @@ -306,6 +305,10 @@ const hostMessaging = new class HostMessaging { } handlers.push(handler); } + + signalReady() { + window.parent.postMessage({ target: ID, channel: 'webview-ready', data: {} }, parentOrigin, [this.channel.port2]); + } }(); const unloadMonitor = new class { @@ -1046,6 +1049,5 @@ onDomReady(() => { } }; - // signal ready - hostMessaging.postMessage('webview-ready', {}); + hostMessaging.signalReady(); }); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 2b27a352c9d83..133ab890ec6a5 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -134,7 +134,8 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD private readonly _onDidHtmlChange: Emitter = this._register(new Emitter()); protected readonly onDidHtmlChange = this._onDidHtmlChange.event; - private readonly _messageHandlers = new Map void>>(); + private messagePort?: MessagePort; + private readonly _messageHandlers = new Map void>>(); protected readonly _webviewFindWidget: WebviewFindWidget | undefined; public readonly checkImeCompletionState = true; @@ -176,17 +177,39 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this._element = this.createElement(options, contentOptions); - const subscription = this._register(this.on(WebviewMessageChannels.webviewReady, () => { - this._logService.debug(`Webview(${this.id}): webview ready`); - this.element?.classList.add('ready'); + const subscription = this._register(addDisposableListener(window, 'message', (e: MessageEvent) => { + if (e?.data?.target !== this.iframeId) { + return; + } - if (this._state.type === WebviewState.Type.Initializing) { - this._state.pendingMessages.forEach(({ channel, data }) => this.doPostMessage(channel, data)); + if (e.origin !== this.webviewContentOrigin) { + console.log(`Skipped renderer receiving message due to mismatched origins: ${e.origin} ${this.webviewContentOrigin}`); + return; } - this._state = WebviewState.Ready; - subscription.dispose(); + if (e.data.channel === WebviewMessageChannels.webviewReady) { + if (this.messagePort) { + return; + } + + this._logService.debug(`Webview(${this.id}): webview ready`); + + this.messagePort = e.ports[0]; + this.messagePort.onmessage = (e) => { + const handlers = this._messageHandlers.get(e.data.channel); + handlers?.forEach(handler => handler(e.data.data, e)); + }; + + this.element?.classList.add('ready'); + + if (this._state.type === WebviewState.Type.Initializing) { + this._state.pendingMessages.forEach(({ channel, data }) => this.doPostMessage(channel, data)); + } + this._state = WebviewState.Ready; + + subscription.dispose(); + } })); this._register(this.on(WebviewMessageChannels.noCspFound, () => { @@ -318,18 +341,6 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD } })); - this._register(addDisposableListener(window, 'message', e => { - if (e?.data?.target === this.iframeId) { - if (e.origin !== this.webviewContentOrigin) { - console.log(`Skipped renderer receiving message due to mismatched origins: ${e.origin} ${this.webviewContentOrigin}`); - return; - } - - const handlers = this._messageHandlers.get(e.data.channel); - handlers?.forEach(handler => handler(e.data.data)); - } - })); - if (options.enableFindWidget) { this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); this.styledFindWidget(); @@ -342,6 +353,8 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this.element?.remove(); this._element = undefined; + this.messagePort = undefined; + this._onDidDispose.fire(); this._resourceLoadingCts.dispose(true); @@ -468,12 +481,12 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD } private doPostMessage(channel: string, data?: any): void { - if (this.element) { - this.element.contentWindow!.postMessage({ channel, args: data }, this.webviewContentEndpoint); + if (this.element && this.messagePort) { + this.messagePort.postMessage({ channel, args: data }); } } - protected on(channel: WebviewMessageChannels, handler: (data: T) => void): IDisposable { + protected on(channel: WebviewMessageChannels, handler: (data: T, e: MessageEvent) => void): IDisposable { let handlers = this._messageHandlers.get(channel); if (!handlers) { handlers = new Set(); From 26b621a77334ca0f45dbe2cc36276580fe9c6d23 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 7 Dec 2021 12:08:39 -0800 Subject: [PATCH 0378/2210] Don't reject status indicator promise Fixes #138453 Looks like `withProgress` does not handle rejections. To dismiss the indicator, we can simply resolve the promise instead --- .../src/typescriptServiceClient.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 9f484dd6f9dfe..f84892cc1cc8f 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -1059,13 +1059,12 @@ function getDignosticsKind(event: Proto.Event) { } class ServerInitializingIndicator extends Disposable { - private _task?: { project: string | undefined, resolve: () => void, reject: (error: Error) => void }; + + private _task?: { project: string | undefined, resolve: () => void }; public reset(): void { if (this._task) { - const error = new Error('Canceled'); - error.name = error.message; - this._task.reject(error); + this._task.resolve(); this._task = undefined; } } @@ -1081,8 +1080,8 @@ class ServerInitializingIndicator extends Disposable { vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: localize('serverLoading.progress', "Initializing JS/TS language features"), - }, () => new Promise((resolve, reject) => { - this._task = { project: projectName, resolve, reject }; + }, () => new Promise(resolve => { + this._task = { project: projectName, resolve }; })); } From 224701f74d0f275bbe814f3689729ec323499478 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 7 Dec 2021 12:25:35 -0800 Subject: [PATCH 0379/2210] try remove timeouts in smoke tests for quickaccess (#138615) --- test/automation/src/quickaccess.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts index 17a7c61cd1202..1a97e561d9b6a 100644 --- a/test/automation/src/quickaccess.ts +++ b/test/automation/src/quickaccess.ts @@ -72,7 +72,6 @@ export class QuickAccess { } await this.quickInput.closeQuickInput(); - await new Promise(c => setTimeout(c, 500)); } if (!fileFound) { @@ -115,7 +114,6 @@ export class QuickAccess { } await this.quickInput.closeQuickInput(); - await new Promise(c => setTimeout(c, 500)); } } } From 2edff154817226edc779a87fc5ebc8b57703786b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 7 Dec 2021 13:02:45 -0800 Subject: [PATCH 0380/2210] Temporarily skip webview tests We need to bump the version of the webview src pulled in, but this requires getting a good build first --- .../vscode-api-tests/src/singlefolder-tests/webview.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 86d3c8c22f368..69ac05f3d3c52 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -18,7 +18,7 @@ function workspaceFile(...segments: string[]) { const testDocument = workspaceFile('bower.json'); -suite('vscode API - webview', () => { +suite.skip('vscode API - webview', () => { const disposables: vscode.Disposable[] = []; function _register(disposable: T) { From 9acd320edad7cea2c062d339fa04822c5eeb9e1d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 7 Dec 2021 13:28:32 -0800 Subject: [PATCH 0381/2210] Also skip notebook test temporarily --- .../vscode-api-tests/src/singlefolder-tests/window.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 165773c832af5..f92f01e3abc8f 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -371,7 +371,7 @@ suite('vscode API - window', () => { }); //#region Tabs API tests - test('Tabs - Ensure tabs getter is correct', async () => { + test.skip('Tabs - Ensure tabs getter is correct', async () => { const [docA, docB, docC, notebookDoc] = await Promise.all([ workspace.openTextDocument(await createRandomFile()), workspace.openTextDocument(await createRandomFile()), From 21e0035bdbe8714d73ad3ee80e5d0b3e409aefa1 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Tue, 7 Dec 2021 13:41:09 -0800 Subject: [PATCH 0382/2210] Refresh Emmet updateImageSize tests Fixes #138499 --- extensions/emmet/package.json | 2 +- extensions/emmet/src/imageSizeHelper.ts | 36 ++++++---- .../emmet/src/test/updateImageSize.test.ts | 66 ++++++++++--------- extensions/emmet/src/typings/image-size.d.ts | 23 ------- extensions/emmet/src/updateImageSize.ts | 6 +- extensions/emmet/yarn.lock | 22 +++++-- 6 files changed, 80 insertions(+), 75 deletions(-) delete mode 100644 extensions/emmet/src/typings/image-size.d.ts diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 3dfc882b432af..4b303046a2674 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -508,7 +508,7 @@ "@emmetio/html-matcher": "^0.3.3", "@emmetio/math-expression": "^1.0.4", "@vscode/emmet-helper": "^2.3.0", - "image-size": "^0.5.2", + "image-size": "~1.0.0", "vscode-languageserver-textdocument": "^1.0.1" }, "capabilities": { diff --git a/extensions/emmet/src/imageSizeHelper.ts b/extensions/emmet/src/imageSizeHelper.ts index c761b095b5e4c..b012ab624ee34 100644 --- a/extensions/emmet/src/imageSizeHelper.ts +++ b/extensions/emmet/src/imageSizeHelper.ts @@ -8,16 +8,23 @@ import * as path from 'path'; import * as http from 'http'; import * as https from 'https'; -import { parse as parseUrl } from 'url'; -import * as sizeOf from 'image-size'; +import { URL } from 'url'; +import { imageSize } from 'image-size'; +import { ISizeCalculationResult } from 'image-size/dist/types/interface'; const reUrl = /^https?:/; +export type ImageInfoWithScale = { + realWidth: number, + realHeight: number, + width: number, + height: number +}; /** * Get size of given image file. Supports files from local filesystem, * as well as URLs */ -export function getImageSize(file: string) { +export function getImageSize(file: string): Promise { file = file.replace(/^file:\/\//, ''); return reUrl.test(file) ? getImageSizeFromURL(file) : getImageSizeFromFile(file); } @@ -25,7 +32,7 @@ export function getImageSize(file: string) { /** * Get image size from file on local file system */ -function getImageSizeFromFile(file: string) { +function getImageSizeFromFile(file: string): Promise { return new Promise((resolve, reject) => { const isDataUrl = file.match(/^data:.+?;base64,/); @@ -33,13 +40,13 @@ function getImageSizeFromFile(file: string) { // NB should use sync version of `sizeOf()` for buffers try { const data = Buffer.from(file.slice(isDataUrl[0].length), 'base64'); - return resolve(sizeForFileName('', sizeOf(data))); + return resolve(sizeForFileName('', imageSize(data))); } catch (err) { return reject(err); } } - sizeOf(file, (err: any, size: any) => { + imageSize(file, (err: Error | null, size?: ISizeCalculationResult) => { if (err) { reject(err); } else { @@ -52,9 +59,9 @@ function getImageSizeFromFile(file: string) { /** * Get image size from given remove URL */ -function getImageSizeFromURL(urlStr: string) { +function getImageSizeFromURL(urlStr: string): Promise { return new Promise((resolve, reject) => { - const url = parseUrl(urlStr); + const url = new URL(urlStr); const getTransport = url.protocol === 'https:' ? https.get : http.get; if (!url.pathname) { @@ -62,13 +69,13 @@ function getImageSizeFromURL(urlStr: string) { } const urlPath: string = url.pathname; - getTransport(url as any, resp => { + getTransport(url, resp => { const chunks: Buffer[] = []; let bufSize = 0; const trySize = (chunks: Buffer[]) => { try { - const size = sizeOf(Buffer.concat(chunks, bufSize)); + const size: ISizeCalculationResult = imageSize(Buffer.concat(chunks, bufSize)); resp.removeListener('data', onData); resp.destroy(); // no need to read further resolve(sizeForFileName(path.basename(urlPath), size)); @@ -90,8 +97,7 @@ function getImageSizeFromURL(urlStr: string) { resp.removeListener('data', onData); reject(err); }); - }) - .once('error', reject); + }).once('error', reject); }); } @@ -99,10 +105,14 @@ function getImageSizeFromURL(urlStr: string) { * Returns size object for given file name. If file name contains `@Nx` token, * the final dimentions will be downscaled by N */ -function sizeForFileName(fileName: string, size: any) { +function sizeForFileName(fileName: string, size?: ISizeCalculationResult): ImageInfoWithScale | undefined { const m = fileName.match(/@(\d+)x\./); const scale = m ? +m[1] : 1; + if (!size || !size.width || !size.height) { + return; + } + return { realWidth: size.width, realHeight: size.height, diff --git a/extensions/emmet/src/test/updateImageSize.test.ts b/extensions/emmet/src/test/updateImageSize.test.ts index 9023285807b9d..37f5653c574b4 100644 --- a/extensions/emmet/src/test/updateImageSize.test.ts +++ b/extensions/emmet/src/test/updateImageSize.test.ts @@ -9,22 +9,26 @@ import { Selection } from 'vscode'; import { withRandomFileEditor, closeAllEditors } from './testUtils'; import { updateImageSize } from '../updateImageSize'; -suite.skip('Tests for Emmet actions on html tags', () => { +suite('Tests for Emmet actions on html tags', () => { teardown(closeAllEditors); + const imageUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAHYcAAB2HAY/l8WUAAAATSURBVBhXY/jPwADGDP////8PAB/uBfuDMzhuAAAAAElFTkSuQmCC'; + const imageWidth = 2; + const imageHeight = 2; + test('update image css with multiple cursors in css file', () => { const cssContents = ` .one { margin: 10px; padding: 10px; - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + background-image: url('${imageUrl}'); } .two { - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + background-image: url('${imageUrl}'); height: 42px; } .three { - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + background-image: url('${imageUrl}'); width: 42px; } `; @@ -32,19 +36,19 @@ suite.skip('Tests for Emmet actions on html tags', () => { .one { margin: 10px; padding: 10px; - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); - width: 1024px; - height: 1024px; + background-image: url('${imageUrl}'); + width: ${imageWidth}px; + height: ${imageHeight}px; } .two { - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); - width: 1024px; - height: 1024px; + background-image: url('${imageUrl}'); + width: ${imageWidth}px; + height: ${imageHeight}px; } .three { - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); - height: 1024px; - width: 1024px; + background-image: url('${imageUrl}'); + height: ${imageHeight}px; + width: ${imageWidth}px; } `; return withRandomFileEditor(cssContents, 'css', (editor, doc) => { @@ -68,14 +72,14 @@ suite.skip('Tests for Emmet actions on html tags', () => { .one { margin: 10px; padding: 10px; - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + background-image: url('${imageUrl}'); } .two { - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + background-image: url('${imageUrl}'); height: 42px; } .three { - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); + background-image: url('${imageUrl}'); width: 42px; } @@ -87,19 +91,19 @@ suite.skip('Tests for Emmet actions on html tags', () => { .one { margin: 10px; padding: 10px; - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); - width: 1024px; - height: 1024px; + background-image: url('${imageUrl}'); + width: ${imageWidth}px; + height: ${imageHeight}px; } .two { - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); - width: 1024px; - height: 1024px; + background-image: url('${imageUrl}'); + width: ${imageWidth}px; + height: ${imageHeight}px; } .three { - background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png); - height: 1024px; - width: 1024px; + background-image: url('${imageUrl}'); + height: ${imageHeight}px; + width: ${imageWidth}px; } @@ -121,16 +125,16 @@ suite.skip('Tests for Emmet actions on html tags', () => { test('update image size in img tag in html file with multiple cursors', () => { const htmlwithimgtag = ` - - - + + + `; const expectedContents = ` - - - + + + `; return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => { diff --git a/extensions/emmet/src/typings/image-size.d.ts b/extensions/emmet/src/typings/image-size.d.ts deleted file mode 100644 index f9a935ea05028..0000000000000 --- a/extensions/emmet/src/typings/image-size.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Type definitions for image-size -// Project: https://github.com/image-size/image-size -// Definitions by: Elisée MAURER -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -/// - -declare module 'image-size' { - interface ImageInfo { - width: number; - height: number; - type: string; - } - - function sizeOf(path: string): ImageInfo; - function sizeOf(path: string, callback: (err: Error, dimensions: ImageInfo) => void): void; - - function sizeOf(buffer: Buffer): ImageInfo; - - namespace sizeOf { } - - export = sizeOf; -} diff --git a/extensions/emmet/src/updateImageSize.ts b/extensions/emmet/src/updateImageSize.ts index dadfe49fd893d..3a9204c0580f3 100644 --- a/extensions/emmet/src/updateImageSize.ts +++ b/extensions/emmet/src/updateImageSize.ts @@ -7,7 +7,7 @@ import { TextEditor, Position, window, TextEdit } from 'vscode'; import * as path from 'path'; -import { getImageSize } from './imageSizeHelper'; +import { getImageSize, ImageInfoWithScale } from './imageSizeHelper'; import { getFlatNode, iterateCSSToken, getCssPropertyFromRule, isStyleSheet, validate, offsetRangeToVsRange } from './util'; import { HtmlNode, CssToken, HtmlToken, Attribute, Property } from 'EmmetFlatNode'; import { locateFile } from './locateFile'; @@ -108,11 +108,11 @@ function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: ( return locateFile(path.dirname(editor.document.fileName), src) .then(getImageSize) - .then((size: any): TextEdit[] => { + .then((size: ImageInfoWithScale | undefined): TextEdit[] => { // since this action is asynchronous, we have to ensure that editor wasn't // changed and user didn't moved caret outside node const prop = fetchNode(editor, position); - if (prop && getImageSrcCSS(editor, prop, position) === src) { + if (size && prop && getImageSrcCSS(editor, prop, position) === src) { return updateCSSNode(editor, prop, size.width, size.height); } return []; diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index 020bf7b07775a..d6b1633858469 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -78,16 +78,30 @@ emmet@^2.3.0: "@emmetio/abbreviation" "^2.2.2" "@emmetio/css-abbreviation" "^2.1.4" -image-size@^0.5.2: - version "0.5.5" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" - integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= +image-size@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.0.tgz#58b31fe4743b1cec0a0ac26f5c914d3c5b2f0750" + integrity sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw== + dependencies: + queue "6.0.2" + +inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== jsonc-parser@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342" integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + vscode-languageserver-textdocument@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.2.tgz#2f9f6bd5b5eb3d8e21424c0c367009216f016236" From 77aaeaf644c909b9de04f172d21a87ca3eb0afcf Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Fri, 12 Nov 2021 14:28:58 -0800 Subject: [PATCH 0383/2210] Add match-type sorting to filterSettings --- .../preferences/browser/preferencesSearch.ts | 40 ++++++++++++++++--- .../preferences/browser/settingsEditor2.ts | 4 +- .../preferences/common/preferences.ts | 15 ++++++- .../preferences/common/preferencesModels.ts | 16 ++++++-- 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index 1088e9b411828..ff1d33053b306 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ISettingsEditorModel, ISetting, ISettingsGroup, IFilterMetadata, ISearchResult, IGroupFilter, ISettingMatcher, IScoredResults, ISettingMatch, IRemoteSetting, IExtensionSetting } from 'vs/workbench/services/preferences/common/preferences'; +import { ISettingsEditorModel, ISetting, ISettingsGroup, IFilterMetadata, ISearchResult, IGroupFilter, ISettingMatcher, IScoredResults, ISettingMatch, IRemoteSetting, IExtensionSetting, SettingMatchType } from 'vs/workbench/services/preferences/common/preferences'; import { IRange } from 'vs/editor/common/core/range'; import { distinct, top } from 'vs/base/common/arrays'; import * as strings from 'vs/base/common/strings'; @@ -114,7 +114,7 @@ export class LocalSearchProvider implements ISearchProvider { let orderedScore = LocalSearchProvider.START_SCORE; // Sort is not stable const settingMatcher = (setting: ISetting) => { - const matches = new SettingMatches(this._filter, setting, true, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches; + const { matches, matchType } = new SettingMatches(this._filter, setting, true, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)); const score = this._filter === setting.key ? LocalSearchProvider.EXACT_MATCH_SCORE : orderedScore--; @@ -122,6 +122,7 @@ export class LocalSearchProvider implements ISearchProvider { return matches && matches.length ? { matches, + matchType, score } : null; @@ -210,7 +211,8 @@ class RemoteSearchProvider implements ISearchProvider { return { setting, score: remoteSetting.score, - matches: [] // TODO + matches: [], // TODO + matchType: SettingMatchType.None }; }); @@ -338,8 +340,8 @@ class RemoteSearchProvider implements ISearchProvider { scoredResults[getSettingKey(setting.key, 'core')] || // core setting scoredResults[getSettingKey(setting.key)]; // core setting from original prod endpoint if (remoteSetting && remoteSetting.score >= minScore) { - const settingMatches = new SettingMatches(this.options.filter, setting, false, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches; - return { matches: settingMatches, score: remoteSetting.score }; + const { matches, matchType } = new SettingMatches(this.options.filter, setting, false, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)); + return { matches, matchType, score: remoteSetting.score }; } return null; @@ -448,6 +450,7 @@ export class SettingMatches { private readonly valueMatchingWords: Map = new Map(); readonly matches: IRange[]; + matchType: SettingMatchType = SettingMatchType.None; constructor(searchString: string, setting: ISetting, private requireFullQueryMatch: boolean, private searchDescription: boolean, private valuesMatcher: (filter: string, setting: ISetting) => IRange[]) { this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`); @@ -465,6 +468,8 @@ export class SettingMatches { const subSettingValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]); result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettingValueRanges); result.push(...subSettingMatches.matches); + this.refreshMatchType(descriptionRanges.length, keyRanges.length + subSettingKeyRanges.length, subSettingValueRanges.length); + this.matchType |= subSettingMatches.matchType; } } return result; @@ -478,12 +483,14 @@ export class SettingMatches { const settingKeyAsWords: string = setting.key.split('.').join(' '); for (const word of words) { + // Whole word match attempts also take place within this loop. if (this.searchDescription) { for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { const descriptionMatches = matchesWords(word, setting.description[lineIndex], true); if (descriptionMatches) { this.descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex))); } + this.checkForWholeWordMatchType(word, setting.description[lineIndex]); } } @@ -491,6 +498,7 @@ export class SettingMatches { if (keyMatches) { this.keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match))); } + this.checkForWholeWordMatchType(word, settingKeyAsWords); const valueMatches = typeof setting.value === 'string' ? matchesContiguousSubString(word, setting.value) : null; if (valueMatches) { @@ -498,6 +506,9 @@ export class SettingMatches { } else if (schema && schema.enum && schema.enum.some(enumValue => typeof enumValue === 'string' && !!matchesContiguousSubString(word, enumValue))) { this.valueMatchingWords.set(word, []); } + if (typeof setting.value === 'string') { + this.checkForWholeWordMatchType(word, setting.value); + } } const descriptionRanges: IRange[] = []; @@ -522,9 +533,28 @@ export class SettingMatches { valueRanges = this.valuesMatcher(searchString, setting); } + this.refreshMatchType(descriptionRanges.length, keyRanges.length, valueRanges.length); return [...descriptionRanges, ...keyRanges, ...valueRanges]; } + private checkForWholeWordMatchType(singleWordQuery: string, lineToSearch: string) { + if (lineToSearch.toLowerCase().split(' ').includes(singleWordQuery.toLowerCase())) { + this.matchType |= SettingMatchType.WholeWordMatch; + } + } + + private refreshMatchType(descriptionRangesLength: number, keyRangesLength: number, valueRangesLength: number) { + if (descriptionRangesLength) { + this.matchType |= SettingMatchType.DescriptionMatch; + } + if (keyRangesLength) { + this.matchType |= SettingMatchType.KeyMatch; + } + if (valueRangesLength) { + this.matchType |= SettingMatchType.ValueMatch; + } + } + private getRangesForWords(words: string[], from: Map, others: Map[]): IRange[] { const result: IRange[] = []; for (const word of words) { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 838eb77804d8e..8c9827288bdc6 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -46,7 +46,7 @@ import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browse import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; +import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync'; @@ -1261,7 +1261,7 @@ export class SettingsEditor2 extends EditorPane { for (const g of this.defaultSettingsEditorModel.settingsGroups.slice(1)) { for (const sect of g.sections) { for (const setting of sect.settings) { - fullResult.filterMatches.push({ setting, matches: [], score: 0 }); + fullResult.filterMatches.push({ setting, matches: [], matchType: SettingMatchType.None, score: 0 }); } } } diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 2857413256bb7..83530c18e8b2b 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -115,9 +115,22 @@ export interface IFilterResult { exactMatch?: boolean; } +/** + * The ways a setting could match a query, + * sorted in increasing order of relevance. + */ +export enum SettingMatchType { + None = 0, + DescriptionMatch = 1 << 0, + ValueMatch = 1 << 1, + KeyMatch = 1 << 2, + WholeWordMatch = 1 << 3 +} + export interface ISettingMatch { setting: ISetting; matches: IRange[] | null; + matchType: SettingMatchType; score: number; } @@ -157,7 +170,7 @@ export interface IPreferencesEditorModel { } export type IGroupFilter = (group: ISettingsGroup) => boolean | null; -export type ISettingMatcher = (setting: ISetting, group: ISettingsGroup) => { matches: IRange[], score: number } | null; +export type ISettingMatcher = (setting: ISetting, group: ISettingsGroup) => { matches: IRange[], matchType: SettingMatchType, score: number } | null; export interface ISettingsEditorModel extends IPreferencesEditorModel { readonly onDidChangeGroups: Event; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 87a08ffaaeb7c..a0f09cfb4016d 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -19,7 +19,7 @@ import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPrope import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup, SettingMatchType } from 'vs/workbench/services/preferences/common/preferences'; import { withNullAsUndefined, isArray } from 'vs/base/common/types'; import { FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation'; @@ -70,14 +70,24 @@ export abstract class AbstractSettingsModel extends EditorModel { filterMatches.push({ setting, matches: settingMatchResult && settingMatchResult.matches, - score: settingMatchResult ? settingMatchResult.score : 0 + matchType: settingMatchResult?.matchType ?? SettingMatchType.None, + score: settingMatchResult?.score ?? 0 }); } } } } - return filterMatches.sort((a, b) => b.score - a.score); + return filterMatches.sort((a, b) => { + // Sort by match type if the match types are not equal. + // The priority of the match type is given by the SettingMatchType enum. + // If they're equal, fall back to the "stable sort" counter score. + if (a.matchType !== b.matchType) { + return b.matchType - a.matchType; + } else { + return b.score - a.score; + } + }); } getPreference(key: string): ISetting | undefined { From df97a577be1d3f8848e7bb1b8f97fe669086af71 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Tue, 16 Nov 2021 08:24:50 -0800 Subject: [PATCH 0384/2210] Fix whole word matching strategy --- .../contrib/preferences/browser/preferencesSearch.ts | 6 +++++- .../services/preferences/common/preferencesModels.ts | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index ff1d33053b306..2e226c678b5e9 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -538,7 +538,11 @@ export class SettingMatches { } private checkForWholeWordMatchType(singleWordQuery: string, lineToSearch: string) { - if (lineToSearch.toLowerCase().split(' ').includes(singleWordQuery.toLowerCase())) { + // Trim excess ending characters off the query. + singleWordQuery = singleWordQuery.toLowerCase().trimEnd().replace(/[-\._]+$/, ''); + lineToSearch = lineToSearch.toLowerCase(); + const singleWordRegex = new RegExp(`\\b${singleWordQuery}\\b`); + if (singleWordRegex.test(lineToSearch)) { this.matchType |= SettingMatchType.WholeWordMatch; } } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index a0f09cfb4016d..139be78f14527 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -78,7 +78,7 @@ export abstract class AbstractSettingsModel extends EditorModel { } } - return filterMatches.sort((a, b) => { + filterMatches.sort((a, b) => { // Sort by match type if the match types are not equal. // The priority of the match type is given by the SettingMatchType enum. // If they're equal, fall back to the "stable sort" counter score. @@ -88,6 +88,7 @@ export abstract class AbstractSettingsModel extends EditorModel { return b.score - a.score; } }); + return filterMatches; } getPreference(key: string): ISetting | undefined { From 3471baf4d40a087f0444be00b7bd720fcfe5d070 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Thu, 18 Nov 2021 08:40:08 -0800 Subject: [PATCH 0385/2210] Remove ordering by value and description --- .../preferences/browser/preferencesSearch.ts | 14 ++++---------- .../services/preferences/common/preferences.ts | 7 +++---- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index 2e226c678b5e9..471d1713868cd 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -468,7 +468,7 @@ export class SettingMatches { const subSettingValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]); result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettingValueRanges); result.push(...subSettingMatches.matches); - this.refreshMatchType(descriptionRanges.length, keyRanges.length + subSettingKeyRanges.length, subSettingValueRanges.length); + this.refreshMatchType(keyRanges.length + subSettingKeyRanges.length); this.matchType |= subSettingMatches.matchType; } } @@ -533,13 +533,13 @@ export class SettingMatches { valueRanges = this.valuesMatcher(searchString, setting); } - this.refreshMatchType(descriptionRanges.length, keyRanges.length, valueRanges.length); + this.refreshMatchType(keyRanges.length); return [...descriptionRanges, ...keyRanges, ...valueRanges]; } private checkForWholeWordMatchType(singleWordQuery: string, lineToSearch: string) { // Trim excess ending characters off the query. - singleWordQuery = singleWordQuery.toLowerCase().trimEnd().replace(/[-\._]+$/, ''); + singleWordQuery = singleWordQuery.toLowerCase().replace(/[\s-\._]+$/, ''); lineToSearch = lineToSearch.toLowerCase(); const singleWordRegex = new RegExp(`\\b${singleWordQuery}\\b`); if (singleWordRegex.test(lineToSearch)) { @@ -547,16 +547,10 @@ export class SettingMatches { } } - private refreshMatchType(descriptionRangesLength: number, keyRangesLength: number, valueRangesLength: number) { - if (descriptionRangesLength) { - this.matchType |= SettingMatchType.DescriptionMatch; - } + private refreshMatchType(keyRangesLength: number) { if (keyRangesLength) { this.matchType |= SettingMatchType.KeyMatch; } - if (valueRangesLength) { - this.matchType |= SettingMatchType.ValueMatch; - } } private getRangesForWords(words: string[], from: Map, others: Map[]): IRange[] { diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 83530c18e8b2b..8e31c1bc7f726 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -118,13 +118,12 @@ export interface IFilterResult { /** * The ways a setting could match a query, * sorted in increasing order of relevance. + * For now, ignore description and value matches. */ export enum SettingMatchType { None = 0, - DescriptionMatch = 1 << 0, - ValueMatch = 1 << 1, - KeyMatch = 1 << 2, - WholeWordMatch = 1 << 3 + WholeWordMatch = 1 << 0, + KeyMatch = 1 << 1 } export interface ISettingMatch { From fc4f41d9fcc7d0c4d964718f2565e68813c92a03 Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Tue, 7 Dec 2021 22:42:47 +0000 Subject: [PATCH 0386/2210] Allow extensions to be able to make requests to the typescript extension's tsserver via commands (#138279) * Allow extensions to be able to make requests to the typescript extension's tsserver via the command system * Adds allowlisting the commands Co-authored-by: Matt Bierner --- .../typescript-language-features/package.json | 3 +- .../src/commands/commandManager.ts | 2 +- .../src/commands/index.ts | 2 + .../src/commands/tsserverRequests.ts | 43 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 extensions/typescript-language-features/src/commands/tsserverRequests.ts diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index b5e11e8892abe..39fe8796f58e3 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -63,10 +63,11 @@ "onCommand:javascript.goToProjectConfig", "onCommand:typescript.goToProjectConfig", "onCommand:typescript.openTsServerLog", - "onTaskType:typescript", + "onCommand:typescript.tsserverRequest", "onCommand:_typescript.configurePlugin", "onCommand:_typescript.learnMoreAboutRefactorings", "onCommand:typescript.fileReferences", + "onTaskType:typescript", "onLanguage:jsonc" ], "main": "./out/extension", diff --git a/extensions/typescript-language-features/src/commands/commandManager.ts b/extensions/typescript-language-features/src/commands/commandManager.ts index b7b58bf7edee3..ccc42b860b125 100644 --- a/extensions/typescript-language-features/src/commands/commandManager.ts +++ b/extensions/typescript-language-features/src/commands/commandManager.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; export interface Command { readonly id: string; - execute(...args: any[]): void; + execute(...args: any[]): void | any; } export class CommandManager { diff --git a/extensions/typescript-language-features/src/commands/index.ts b/extensions/typescript-language-features/src/commands/index.ts index 74faf7a46f65d..4b6f8d36ac3af 100644 --- a/extensions/typescript-language-features/src/commands/index.ts +++ b/extensions/typescript-language-features/src/commands/index.ts @@ -15,6 +15,7 @@ import { OpenTsServerLogCommand } from './openTsServerLog'; import { ReloadJavaScriptProjectsCommand, ReloadTypeScriptProjectsCommand } from './reloadProject'; import { RestartTsServerCommand } from './restartTsServer'; import { SelectTypeScriptVersionCommand } from './selectTypeScriptVersion'; +import { TSServerRequestCommand } from './tsserverRequests'; export function registerBaseCommands( commandManager: CommandManager, @@ -31,4 +32,5 @@ export function registerBaseCommands( commandManager.register(new JavaScriptGoToProjectConfigCommand(activeJsTsEditorTracker, lazyClientHost)); commandManager.register(new ConfigurePluginCommand(pluginManager)); commandManager.register(new LearnMoreAboutRefactoringsCommand()); + commandManager.register(new TSServerRequestCommand(lazyClientHost)); } diff --git a/extensions/typescript-language-features/src/commands/tsserverRequests.ts b/extensions/typescript-language-features/src/commands/tsserverRequests.ts new file mode 100644 index 0000000000000..ba545dfbea9fe --- /dev/null +++ b/extensions/typescript-language-features/src/commands/tsserverRequests.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TypeScriptRequests } from '../typescriptService'; +import TypeScriptServiceClientHost from '../typeScriptServiceClientHost'; +import { nulToken } from '../utils/cancellation'; +import { Lazy } from '../utils/lazy'; +import { Command } from './commandManager'; + +export class TSServerRequestCommand implements Command { + public readonly id = 'typescript.tsserverRequest'; + + public constructor( + private readonly lazyClientHost: Lazy + ) { } + + public execute(requestID: keyof TypeScriptRequests, args?: any, config?: any) { + // A cancellation token cannot be passed through the command infrastructure + const token = nulToken; + + // The list can be found in the TypeScript compiler as `const enum CommandTypes`, + // to avoid extensions making calls which could affect the internal tsserver state + // these are only read-y sorts of commands + const allowList = [ + // Seeing the JS/DTS output for a file + 'emit-output', + // Grabbing a file's diagnostics + 'semanticDiagnosticsSync', + 'syntacticDiagnosticsSync', + 'suggestionDiagnosticsSync', + // Introspecting code at a position + 'quickinfo', + 'quickinfo-full', + 'completionInfo' + ]; + + if (!allowList.includes(requestID)) { return; } + return this.lazyClientHost.value.serviceClient.execute(requestID, args, token, config); + } +} + From c98dde4409648e015ab527f74968475454cd2bf2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 7 Dec 2021 16:44:11 +0100 Subject: [PATCH 0387/2210] #138511 use json stringify --- .../userDataSync/common/extensionsSync.ts | 20 ++++++++----------- .../userDataSync/common/globalStateSync.ts | 18 +++++++---------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index c17aa428dd112..6134eeacd1d52 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -8,8 +8,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IStringDictionary } from 'vs/base/common/collections'; import { getErrorMessage } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; -import { applyEdits } from 'vs/base/common/jsonEdit'; -import { format } from 'vs/base/common/jsonFormatter'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -149,10 +147,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return [{ skippedExtensions, localResource: this.localResource, - localContent: this.format(localExtensions), + localContent: this.stringify(localExtensions), localExtensions, remoteResource: this.remoteResource, - remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, + remoteContent: remoteExtensions ? this.stringify(remoteExtensions) : null, previewResource: this.previewResource, previewResult, localChange: previewResult.localChange, @@ -182,7 +180,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse preview.push(localExtension); } - return this.format(preview); + return this.stringify(preview); } protected async getMergeResult(resourcePreview: IExtensionResourcePreview, token: CancellationToken): Promise { @@ -286,7 +284,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const installedExtensions = await this.extensionManagementService.getInstalled(); const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier))); - return this.format(localExtensions); + return this.stringify(localExtensions); } if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { @@ -304,7 +302,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (syncData) { switch (this.extUri.basename(uri)) { case 'extensions.json': - return this.format(this.parseExtensions(syncData)); + return this.stringify(this.parseExtensions(syncData)); } } } @@ -312,7 +310,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return null; } - private format(extensions: ISyncExtension[]): string { + private stringify(extensions: ISyncExtension[]): string { extensions.sort((e1, e2) => { if (!e1.identifier.uuid && e2.identifier.uuid) { return -1; @@ -322,9 +320,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } return compare(e1.identifier.id, e2.identifier.id); }); - const content = JSON.stringify(extensions); - const edits = format(content, undefined, {}); - return applyEdits(content, edits); + return JSON.stringify(extensions, null, '\t'); } async hasLocalData(): Promise { @@ -473,7 +469,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const disabledExtensions = this.extensionEnablementService.getDisabledExtensions(); return installedExtensions .map(({ identifier, isBuiltin, manifest, preRelease }) => { - const syncExntesion: ISyncExtensionWithVersion = { identifier, version: manifest.version, preRelease }; + const syncExntesion: ISyncExtensionWithVersion = { identifier, preRelease, version: manifest.version }; if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { syncExntesion.disabled = true; } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 4ba0ee282a45d..bc01d952b10ab 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -9,8 +9,6 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { getErrorMessage } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; -import { applyEdits } from 'vs/base/common/jsonEdit'; -import { format } from 'vs/base/common/jsonFormatter'; import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -45,14 +43,12 @@ export interface IGlobalStateResourcePreview extends IResourcePreview { readonly storageKeys: StorageKeys; } -function formatAndStringify(globalState: IGlobalState): string { +function stringify(globalState: IGlobalState): string { const storageKeys = globalState.storage ? Object.keys(globalState.storage).sort() : []; const storage: IStringDictionary = {}; storageKeys.forEach(key => storage[key] = globalState.storage[key]); globalState.storage = storage; - const content = JSON.stringify(globalState); - const edits = format(content, undefined, {}); - return applyEdits(content, edits); + return JSON.stringify(globalState, null, '\t'); } const GLOBAL_STATE_DATA_VERSION = 1; @@ -128,10 +124,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return [{ localResource: this.localResource, - localContent: formatAndStringify(localGlobalState), + localContent: stringify(localGlobalState), localUserData: localGlobalState, remoteResource: this.remoteResource, - remoteContent: remoteGlobalState ? formatAndStringify(remoteGlobalState) : null, + remoteContent: remoteGlobalState ? stringify(remoteGlobalState) : null, previewResource: this.previewResource, previewResult, localChange: previewResult.localChange, @@ -236,7 +232,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs override async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) { const localGlobalState = await this.getLocalGlobalState(); - return formatAndStringify(localGlobalState); + return stringify(localGlobalState); } if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { @@ -254,7 +250,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (syncData) { switch (this.extUri.basename(uri)) { case 'globalState.json': - return formatAndStringify(JSON.parse(syncData.content)); + return stringify(JSON.parse(syncData.content)); } } } @@ -467,7 +463,7 @@ export class UserDataSyncStoreTypeSynchronizer { // Write the global state to remote const machineId = await getServiceMachineId(this.environmentService, this.fileService, this.storageService); - const syncDataToUpdate: ISyncData = { version: GLOBAL_STATE_DATA_VERSION, machineId, content: formatAndStringify(remoteGlobalState) }; + const syncDataToUpdate: ISyncData = { version: GLOBAL_STATE_DATA_VERSION, machineId, content: stringify(remoteGlobalState) }; await this.userDataSyncStoreClient.write(SyncResource.GlobalState, JSON.stringify(syncDataToUpdate), globalStateUserData.ref, syncHeaders); } From 2189e66f27c00b7e8e41dd09a416f23a48c66206 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 00:42:16 +0100 Subject: [PATCH 0388/2210] proper fix for #138515 --- .../common/abstractSynchronizer.ts | 14 +- .../userDataSync/common/extensionsSync.ts | 1 - .../userDataSync/common/settingsSync.ts | 48 +-- .../common/userDataSyncService.ts | 359 ++++++++++++------ .../test/common/globalStateSync.test.ts | 5 +- .../test/common/keybindingsSync.test.ts | 5 +- .../test/common/settingsSync.test.ts | 7 +- .../test/common/snippetsSync.test.ts | 5 +- .../test/common/synchronizer.test.ts | 15 +- .../test/common/userDataSyncClient.ts | 6 +- .../test/common/userDataSyncService.test.ts | 65 +++- .../browser/userDataSyncWorkbenchService.ts | 4 - 12 files changed, 330 insertions(+), 204 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 01ca31c0d887a..1ce5767d00010 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -139,12 +139,8 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService); } - protected isEnabled(): boolean { return this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(this.resource); } - protected async triggerLocalChange(): Promise { - if (this.isEnabled()) { - this.localChangeTriggerScheduler.schedule(); - } + this.localChangeTriggerScheduler.schedule(); } protected async doTriggerLocalChange(): Promise { @@ -210,14 +206,6 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa try { this.syncHeaders = { ...headers }; - if (!this.isEnabled()) { - if (this.status !== SyncStatus.Idle) { - await this.stop(); - } - this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is disabled.`); - return null; - } - if (this.status === SyncStatus.HasConflicts) { this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`); return this.syncPreviewPromise; diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 6134eeacd1d52..d63631406616a 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -87,7 +87,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse /* Version 5: Introduce extension state */ protected readonly version: number = 5; - protected override isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'extensions.json'); private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index c3c9a6e653229..9de908f8fbb67 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -6,8 +6,6 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; -import { applyEdits, setProperty } from 'vs/base/common/jsonEdit'; -import { Edit } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -22,7 +20,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { edit } from 'vs/platform/userDataSync/common/content'; import { getIgnoredSettings, isEmpty, merge, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { Change, CONFIGURATION_SYNC_STORE_KEY, IRemoteUserData, ISyncData, ISyncResourceHandle, IUserDataManifest, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, CONFIGURATION_SYNC_STORE_KEY, IRemoteUserData, ISyncResourceHandle, IUserDataManifest, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; interface ISettingsResourcePreview extends IFileResourcePreview { previewResult: IMergeResult; @@ -339,38 +337,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } } - async recoverSettings(): Promise { - try { - const fileContent = await this.getLocalFileContent(); - if (!fileContent) { - return; - } - - const syncData: ISyncData = JSON.parse(fileContent.value.toString()); - if (!isSyncData(syncData)) { - return; - } - - this.telemetryService.publicLog2('sync/settingsCorrupted'); - const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); - if (!settingsSyncContent || !settingsSyncContent.settings) { - return; - } - - let settings = settingsSyncContent.settings; - const formattingOptions = await this.getFormattingOptions(); - for (const key in syncData) { - if (['version', 'content', 'machineId'].indexOf(key) === -1 && (syncData as any)[key] !== undefined) { - const edits: Edit[] = setProperty(settings, [key], (syncData as any)[key], formattingOptions); - if (edits.length) { - settings = applyEdits(settings, edits); - } - } - } - - await this.fileService.writeFile(this.file, VSBuffer.fromString(settings)); - } catch (e) {/* ignore */ } - } } export class SettingsInitializer extends AbstractInitializer { @@ -421,15 +387,3 @@ export class SettingsInitializer extends AbstractInitializer { } } - -function isSyncData(thing: any): thing is ISyncData { - if (thing - && (thing.version !== undefined && typeof thing.version === 'number') - && (thing.content !== undefined && typeof thing.content === 'string') - && (thing.machineId !== undefined && typeof thing.machineId === 'string') - ) { - return true; - } - - return false; -} diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index afd80065149f6..fd3b6f17b7a75 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -7,14 +7,15 @@ import { equals } from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; +import { isUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IHeaders } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -23,7 +24,7 @@ import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalS import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync'; -import { Change, createSyncHeaders, IManualSyncTask, IResourcePreview, ISyncResourceHandle, ISyncResourcePreview, ISyncTask, IUserDataManifest, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, MergeState, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, IManualSyncTask, IResourcePreview, ISyncResourceHandle, ISyncResourcePreview, ISyncTask, IUserDataManifest, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, MergeState, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE } from 'vs/platform/userDataSync/common/userDataSync'; type SyncErrorClassification = { code: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -40,14 +41,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ _serviceBrand: any; - private readonly synchronisers: IUserDataSynchroniser[]; - private _status: SyncStatus = SyncStatus.Uninitialized; get status(): SyncStatus { return this._status; } private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - readonly onDidChangeLocal: Event; + private _onDidChangeLocal = this._register(new Emitter()); + readonly onDidChangeLocal = this._onDidChangeLocal.event; private _conflicts: [SyncResource, IResourcePreview[]][] = []; get conflicts(): [SyncResource, IResourcePreview[]][] { return this._conflicts; } @@ -68,11 +68,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidResetRemote = this._register(new Emitter()); readonly onDidResetRemote = this._onDidResetRemote.event; - private readonly settingsSynchroniser: SettingsSynchroniser; - private readonly keybindingsSynchroniser: KeybindingsSynchroniser; - private readonly snippetsSynchroniser: SnippetsSynchroniser; - private readonly extensionsSynchroniser: ExtensionsSynchroniser; - private readonly globalStateSynchroniser: GlobalStateSynchroniser; + private readonly synchronizers = this._register(new MutableDisposable()); constructor( @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @@ -82,27 +78,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IStorageService private readonly storageService: IStorageService, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, ) { super(); - this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); - this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); - this.snippetsSynchroniser = this._register(this.instantiationService.createInstance(SnippetsSynchroniser)); - this.globalStateSynchroniser = this._register(this.instantiationService.createInstance(GlobalStateSynchroniser)); - this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); - this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.globalStateSynchroniser, this.extensionsSynchroniser]; - this.updateStatus(); - - if (this.userDataSyncStoreManagementService.userDataSyncStore) { - this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); - this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeConflicts, () => undefined)))(() => this.updateConflicts())); - } - + this.updateStatus([]); this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined); - this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.resource))); } async createSyncTask(manifest: IUserDataManifest | null, disableCache?: boolean): Promise { - await this.checkEnablement(); + this.checkEnablement(); const executionId = generateUuid(); try { @@ -119,14 +103,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ let executed = false; const that = this; + const synchronizers = this.getEnabledSynchronizers(); let cancellablePromise: CancelablePromise | undefined; return { manifest, - run(): Promise { + async run(): Promise { if (executed) { throw new Error('Can run a task only once'); } - cancellablePromise = createCancelablePromise(token => that.sync(manifest, executionId, token)); + cancellablePromise = createCancelablePromise(token => that.sync(synchronizers, manifest, executionId, token)); return cancellablePromise.finally(() => cancellablePromise = undefined); }, async stop(): Promise { @@ -134,14 +119,18 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ cancellablePromise.cancel(); } if (that.status !== SyncStatus.Idle) { - return that.stop(); + return that.stop(synchronizers); } } }; } async createManualSyncTask(): Promise { - await this.checkEnablement(); + this.checkEnablement(); + + if (this.userDataSyncEnablementService.isEnabled()) { + throw new UserDataSyncError('Cannot start manual sync when sync is enabled', UserDataSyncErrorCode.LocalError); + } const executionId = generateUuid(); const syncHeaders = createSyncHeaders(executionId); @@ -155,21 +144,24 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ throw userDataSyncError; } - return new ManualSyncTask(executionId, manifest, syncHeaders, this.synchronisers, this.configurationService, this.logService); + /* Manual sync shall start on clean local state */ + await this.resetLocal(); + + const enabledSynchronizers = this.getEnabledSynchronizers(); + return new ManualSyncTask(executionId, manifest, syncHeaders, enabledSynchronizers, () => this.resetLocal(), this.configurationService, this.logService); } - private recoveredSettings: boolean = false; - private async sync(manifest: IUserDataManifest | null, executionId: string, token: CancellationToken): Promise { - if (!this.recoveredSettings) { - await this.settingsSynchroniser.recoverSettings(); - this.recoveredSettings = true; - } + private async sync(synchronizers: IUserDataSynchroniser[], manifest: IUserDataManifest | null, executionId: string, token: CancellationToken): Promise { // Return if cancellation is requested if (token.isCancellationRequested) { return; } + if (!synchronizers.length) { + return; + } + const startTime = new Date().getTime(); this._syncErrors = []; try { @@ -180,11 +172,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ const syncHeaders = createSyncHeaders(executionId); - for (const synchroniser of this.synchronisers) { + for (const synchroniser of synchronizers) { // Return if cancellation is requested if (token.isCancellationRequested) { return; } + + // Return if resource is not enabled + if (!this.userDataSyncEnablementService.isResourceEnabled(synchroniser.resource)) { + return; + } + try { await synchroniser.sync(manifest, syncHeaders); } catch (e) { @@ -228,17 +226,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.reportUserDataSyncError(userDataSyncError, executionId); throw userDataSyncError; } finally { - this.updateStatus(); + this.updateStatus(synchronizers); this._onSyncErrors.fire(this._syncErrors); } } - private async stop(): Promise { + private async stop(synchronizers: IUserDataSynchroniser[]): Promise { if (this.status === SyncStatus.Idle) { return; } - for (const synchroniser of this.synchronisers) { + for (const synchroniser of synchronizers) { try { if (synchroniser.status !== SyncStatus.Idle) { await synchroniser.stop(); @@ -251,68 +249,111 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async replace(uri: URI): Promise { - await this.checkEnablement(); - for (const synchroniser of this.synchronisers) { - if (await synchroniser.replace(uri)) { - return; + this.checkEnablement(); + + await this.performSynchronizerAction(async synchronizer => { + if (await synchronizer.replace(uri)) { + return true; } - } + return undefined; + }); } async accept(syncResource: SyncResource, resource: URI, content: string | null | undefined, apply: boolean): Promise { - await this.checkEnablement(); - const synchroniser = this.getSynchroniser(syncResource); - await synchroniser.accept(resource, content); - if (apply) { - await synchroniser.apply(false, createSyncHeaders(generateUuid())); - } + this.checkEnablement(); + + await this.performSynchronizerAction(async synchronizer => { + if (synchronizer.resource === syncResource) { + await synchronizer.accept(resource, content); + if (apply) { + await synchronizer.apply(false, createSyncHeaders(generateUuid())); + } + return true; + } + return undefined; + }); } async resolveContent(resource: URI): Promise { - for (const synchroniser of this.synchronisers) { - const content = await synchroniser.resolveContent(resource); + return this.performSynchronizerAction(async synchronizer => { + const content = await synchronizer.resolveContent(resource); if (content) { return content; } - } - return null; + return undefined; + }); } - getRemoteSyncResourceHandles(resource: SyncResource): Promise { - return this.getSynchroniser(resource).getRemoteSyncResourceHandles(); + async getRemoteSyncResourceHandles(resource: SyncResource): Promise { + const result = await this.performSynchronizerAction(async synchronizer => { + if (synchronizer.resource === resource) { + return synchronizer.getRemoteSyncResourceHandles(); + } + return undefined; + }); + return result || []; } - getLocalSyncResourceHandles(resource: SyncResource): Promise { - return this.getSynchroniser(resource).getLocalSyncResourceHandles(); + async getLocalSyncResourceHandles(resource: SyncResource): Promise { + const result = await this.performSynchronizerAction(async synchronizer => { + if (synchronizer.resource === resource) { + return synchronizer.getLocalSyncResourceHandles(); + } + return undefined; + }); + return result || []; } - getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> { - return this.getSynchroniser(resource).getAssociatedResources(syncResourceHandle); + async getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> { + const result = await this.performSynchronizerAction(async synchronizer => { + if (synchronizer.resource === resource) { + return synchronizer.getAssociatedResources(syncResourceHandle); + } + return undefined; + }); + return result || []; } - getMachineId(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise { - return this.getSynchroniser(resource).getMachineId(syncResourceHandle); + async getMachineId(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise { + const result = await this.performSynchronizerAction(async synchronizer => { + if (synchronizer.resource === resource) { + const result = await synchronizer.getMachineId(syncResourceHandle); + return result || null; + } + return undefined; + }); + return result || undefined; } async hasLocalData(): Promise { - // skip global state synchronizer - const synchronizers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.extensionsSynchroniser]; - for (const synchroniser of synchronizers) { - if (await synchroniser.hasLocalData()) { + const result = await this.performSynchronizerAction(async synchronizer => { + // skip global state synchronizer + if (synchronizer.resource !== SyncResource.GlobalState && await synchronizer.hasLocalData()) { return true; } - } - return false; + return undefined; + }); + return !!result; + } + + async hasPreviouslySynced(): Promise { + const result = await this.performSynchronizerAction(async synchronizer => { + if (await synchronizer.hasPreviouslySynced()) { + return true; + } + return undefined; + }); + return !!result; } async reset(): Promise { - await this.checkEnablement(); + this.checkEnablement(); await this.resetRemote(); await this.resetLocal(); } async resetRemote(): Promise { - await this.checkEnablement(); + this.checkEnablement(); try { await this.userDataSyncStoreService.clear(); this.logService.info('Cleared data on server'); @@ -323,27 +364,38 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async resetLocal(): Promise { - await this.checkEnablement(); + this.checkEnablement(); this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL); - for (const synchroniser of this.synchronisers) { - try { - await synchroniser.resetLocal(); - } catch (e) { - this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`); - this.logService.error(e); + if (this.synchronizers.value) { + for (const synchroniser of this.synchronizers.value.enabled) { + try { + await synchroniser.resetLocal(); + } catch (e) { + this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`); + this.logService.error(e); + } } + this.synchronizers.value = undefined; } this._onDidResetLocal.fire(); this.logService.info('Did reset the local sync state.'); } - async hasPreviouslySynced(): Promise { - for (const synchroniser of this.synchronisers) { - if (await synchroniser.hasPreviouslySynced()) { - return true; + private async performSynchronizerAction(action: (synchroniser: IUserDataSynchroniser) => Promise): Promise { + const disposables = new DisposableStore(); + try { + const synchronizers = this.synchronizers.value || disposables.add(this.instantiationService.createInstance(Synchronizers, () => { }, () => { }, () => { })); + const allSynchronizers = [...synchronizers.enabled, ...synchronizers.disabled.map(syncResource => disposables.add(synchronizers.createSynchronizer(syncResource)))]; + for (const synchronizer of allSynchronizers) { + const result = await action(synchronizer); + if (!isUndefined(result)) { + return result; + } } + return null; + } finally { + disposables.dispose(); } - return false; } private setStatus(status: SyncStatus): void { @@ -357,28 +409,33 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - private updateStatus(): void { - this.updateConflicts(); - const status = this.computeStatus(); + private updateStatus(synchronizers: IUserDataSynchroniser[]): void { + this.updateConflicts(synchronizers); + const status = this.computeStatus(synchronizers); this.setStatus(status); } - private updateConflicts(): void { - const conflicts = this.computeConflicts(); + private updateConflicts(synchronizers: IUserDataSynchroniser[]): void { + const conflicts = this.computeConflicts(synchronizers); if (!equals(this._conflicts, conflicts, ([syncResourceA, conflictsA], [syncResourceB, conflictsB]) => syncResourceA === syncResourceA && equals(conflictsA, conflictsB, (a, b) => isEqual(a.previewResource, b.previewResource)))) { - this._conflicts = this.computeConflicts(); + this._conflicts = this.computeConflicts(synchronizers); this._onDidChangeConflicts.fire(conflicts); } } - private computeStatus(): SyncStatus { + private computeConflicts(synchronizers: IUserDataSynchroniser[]): [SyncResource, IResourcePreview[]][] { + return synchronizers.filter(s => s.status === SyncStatus.HasConflicts) + .map(s => ([s.resource, s.conflicts.map(toStrictResourcePreview)])); + } + + private computeStatus(synchronizers: IUserDataSynchroniser[]): SyncStatus { if (!this.userDataSyncStoreManagementService.userDataSyncStore) { return SyncStatus.Uninitialized; } - if (this.synchronisers.some(s => s.status === SyncStatus.HasConflicts)) { + if (synchronizers.some(s => s.status === SyncStatus.HasConflicts)) { return SyncStatus.HasConflicts; } - if (this.synchronisers.some(s => s.status === SyncStatus.Syncing)) { + if (synchronizers.some(s => s.status === SyncStatus.Syncing)) { return SyncStatus.Syncing; } return SyncStatus.Idle; @@ -404,16 +461,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ }); } - private computeConflicts(): [SyncResource, IResourcePreview[]][] { - return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts) - .map(s => ([s.resource, s.conflicts.map(toStrictResourcePreview)])); - } - - getSynchroniser(source: SyncResource): IUserDataSynchroniser { - return this.synchronisers.find(s => s.resource === source)!; + getEnabledSynchronizers(): IUserDataSynchroniser[] { + if (!this.synchronizers.value) { + this.synchronizers.value = this.instantiationService.createInstance(Synchronizers, synchronizers => this.updateStatus(synchronizers), synchronizers => this.updateConflicts(synchronizers), syncResource => this._onDidChangeLocal.fire(syncResource)); + } + return this.synchronizers.value.enabled; } - private async checkEnablement(): Promise { + private checkEnablement(): void { if (!this.userDataSyncStoreManagementService.userDataSyncStore) { throw new Error('Not enabled'); } @@ -447,6 +502,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { readonly manifest: IUserDataManifest | null, private readonly syncHeaders: IHeaders, private readonly synchronisers: IUserDataSynchroniser[], + private readonly onStop: () => Promise, private readonly configurationService: IConfigurationService, private readonly logService: IUserDataSyncLogService, ) { @@ -622,15 +678,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { } async stop(): Promise { - for (const synchroniser of this.synchronisers) { - try { - await synchroniser.stop(); - } catch (error) { - if (!isPromiseCanceledError(error)) { - this.logService.error(error); - } - } - } + await this.onStop(); this.reset(); } @@ -755,6 +803,97 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { } +class Synchronizers extends Disposable { + + private _enabled: [IUserDataSynchroniser, number, IDisposable][] = []; + get enabled(): IUserDataSynchroniser[] { return this._enabled.sort((a, b) => a[1] - b[1]).map(([synchronizer]) => synchronizer); } + + get disabled(): SyncResource[] { return ALL_SYNC_RESOURCES.filter(syncResource => !this.userDataSyncEnablementService.isResourceEnabled(syncResource)); } + + constructor( + private onDidChangeStatus: (synchronizers: IUserDataSynchroniser[]) => void, + private onDidChangeConflicts: (synchronizers: IUserDataSynchroniser[]) => void, + private onDidChangeLocal: (syncResource: SyncResource) => void, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + super(); + this._register(userDataSyncEnablementService.onDidChangeResourceEnablement(([syncResource, enablement]) => this.onDidChangeResourceEnablement(syncResource, enablement))); + this._register(toDisposable(() => this._enabled.splice(0, this._enabled.length).forEach(([, , disposable]) => disposable.dispose()))); + for (const syncResource of ALL_SYNC_RESOURCES) { + if (userDataSyncEnablementService.isResourceEnabled(syncResource)) { + this.registerSynchronizer(syncResource); + } + } + } + + private onDidChangeResourceEnablement(syncResource: SyncResource, enabled: boolean): void { + if (enabled) { + this.registerSynchronizer(syncResource); + } else { + this.deRegisterSynchronizer(syncResource); + } + } + + private registerSynchronizer(syncResource: SyncResource): void { + if (this._enabled.some(([synchronizer]) => synchronizer.resource === syncResource)) { + return; + } + if (syncResource === SyncResource.Extensions && !this.extensionGalleryService.isEnabled()) { + this.logService.info('Skipping extensions sync because gallery is not configured'); + return; + } + const disposables = new DisposableStore(); + const synchronizer = disposables.add(this.createSynchronizer(syncResource)); + disposables.add(synchronizer.onDidChangeStatus(() => this.onDidChangeStatus(this.enabled))); + disposables.add(synchronizer.onDidChangeConflicts(() => this.onDidChangeConflicts(this.enabled))); + disposables.add(synchronizer.onDidChangeLocal(() => this.onDidChangeLocal(syncResource))); + const order = this.getOrder(syncResource); + this._enabled.push([synchronizer, order, disposables]); + } + + private deRegisterSynchronizer(syncResource: SyncResource): void { + const index = this._enabled.findIndex(([synchronizer]) => synchronizer.resource === syncResource); + if (index !== -1) { + const removed = this._enabled.splice(index, 1); + for (const [synchronizer, , disposable] of removed) { + if (synchronizer.status !== SyncStatus.Idle) { + const hasConflicts = synchronizer.conflicts.length > 0; + synchronizer.stop(); + if (hasConflicts) { + this.onDidChangeConflicts(this.enabled); + } + this.onDidChangeStatus(this.enabled); + } + disposable.dispose(); + } + } + } + + createSynchronizer(syncResource: SyncResource): IUserDataSynchroniser & IDisposable { + switch (syncResource) { + case SyncResource.Settings: return this.instantiationService.createInstance(SettingsSynchroniser); + case SyncResource.Keybindings: return this.instantiationService.createInstance(KeybindingsSynchroniser); + case SyncResource.Snippets: return this.instantiationService.createInstance(SnippetsSynchroniser); + case SyncResource.GlobalState: return this.instantiationService.createInstance(GlobalStateSynchroniser); + case SyncResource.Extensions: return this.instantiationService.createInstance(ExtensionsSynchroniser); + } + } + + private getOrder(syncResource: SyncResource): number { + switch (syncResource) { + case SyncResource.Settings: return 0; + case SyncResource.Keybindings: return 1; + case SyncResource.Snippets: return 2; + case SyncResource.GlobalState: return 3; + case SyncResource.Extensions: return 4; + } + } + +} + function toStrictResourcePreview(resourcePreview: IResourcePreview): IResourcePreview { return { localResource: resourcePreview.localResource, diff --git a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts index 6c86fd4bcfc33..ca67c5487b12d 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -10,8 +10,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; -import { IGlobalState, ISyncData, IUserDataSyncService, IUserDataSyncStoreService, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { IGlobalState, ISyncData, IUserDataSyncStoreService, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; @@ -27,7 +26,7 @@ suite('GlobalStateSync', () => { setup(async () => { testClient = disposableStore.add(new UserDataSyncClient(server)); await testClient.setUp(true); - testObject = (testClient.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.GlobalState) as GlobalStateSynchroniser; + testObject = testClient.getSynchronizer(SyncResource.GlobalState) as GlobalStateSynchroniser; disposableStore.add(toDisposable(() => testClient.instantiationService.get(IUserDataSyncStoreService).clear())); client2 = disposableStore.add(new UserDataSyncClient(server)); diff --git a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index 3b08e719fe00a..710cd03784641 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -10,8 +10,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { getKeybindingsContentFromSyncContent, KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; -import { IUserDataSyncService, IUserDataSyncStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { IUserDataSyncStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; suite('KeybindingsSync', () => { @@ -25,7 +24,7 @@ suite('KeybindingsSync', () => { setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(true); - testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.Keybindings) as KeybindingsSynchroniser; + testObject = client.getSynchronizer(SyncResource.Keybindings) as KeybindingsSynchroniser; disposableStore.add(toDisposable(() => client.instantiationService.get(IUserDataSyncStoreService).clear())); }); diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index 09c7350c3a40d..6c2bfbea3051b 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -13,8 +13,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; import { ISettingsSyncContent, parseSettingsSyncContent, SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; -import { ISyncData, IUserDataSyncService, IUserDataSyncStoreService, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { ISyncData, IUserDataSyncStoreService, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; Registry.as(Extensions.Configuration).registerConfiguration({ @@ -42,7 +41,7 @@ suite('SettingsSync - Auto', () => { setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(true); - testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.Settings) as SettingsSynchroniser; + testObject = client.getSynchronizer(SyncResource.Settings) as SettingsSynchroniser; disposableStore.add(toDisposable(() => client.instantiationService.get(IUserDataSyncStoreService).clear())); }); @@ -506,7 +505,7 @@ suite('SettingsSync - Manual', () => { setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(true); - testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.Settings) as SettingsSynchroniser; + testObject = client.getSynchronizer(SyncResource.Settings) as SettingsSynchroniser; disposableStore.add(toDisposable(() => client.instantiationService.get(IUserDataSyncStoreService).clear())); }); diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index 6ea331ed63a99..7f9bcaecff76d 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -12,8 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync'; -import { IResourcePreview, ISyncData, IUserDataSyncService, IUserDataSyncStoreService, PREVIEW_DIR_NAME, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { IResourcePreview, ISyncData, IUserDataSyncStoreService, PREVIEW_DIR_NAME, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; const tsSnippet1 = `{ @@ -158,7 +157,7 @@ suite('SnippetsSync', () => { setup(async () => { testClient = disposableStore.add(new UserDataSyncClient(server)); await testClient.setUp(true); - testObject = (testClient.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.Snippets) as SnippetsSynchroniser; + testObject = testClient.getSynchronizer(SyncResource.Snippets) as SnippetsSynchroniser; disposableStore.add(toDisposable(() => testClient.instantiationService.get(IUserDataSyncStoreService).clear())); client2 = disposableStore.add(new UserDataSyncClient(server)); diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index be88ca0a233f0..2e0a16e806e0f 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; -import { Change, IRemoteUserData, IResourcePreview as IBaseResourcePreview, IUserDataManifest, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncStoreService, MergeState, SyncResource, SyncStatus, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IResourcePreview as IBaseResourcePreview, IUserDataManifest, IUserDataSyncConfiguration, IUserDataSyncStoreService, MergeState, SyncResource, SyncStatus, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; interface ITestResourcePreview extends IResourcePreview { @@ -256,19 +256,6 @@ suite('TestSynchronizer - Auto Sync', () => { await testObject.stop(); }); - test('sync should not run if disabled', async () => { - const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); - client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resource, false); - - const actual: SyncStatus[] = []; - disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); - - await testObject.sync(await client.manifest()); - - assert.deepStrictEqual(actual, []); - assert.deepStrictEqual(testObject.status, SyncStatus.Idle); - }); - test('sync should not run if there are conflicts', async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings)); testObject.syncResult = { hasConflicts: true, hasError: false }; diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 04b0dd817efdc..194c6892996ca 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -35,7 +35,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { ALL_SYNC_RESOURCES, getDefaultIgnoredSettings, IUserData, IUserDataManifest, IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration, ServerResource, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, getDefaultIgnoredSettings, IUserData, IUserDataManifest, IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration, ServerResource, SyncResource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; @@ -145,6 +145,10 @@ export class UserDataSyncClient extends Disposable { return this.instantiationService.get(IUserDataSyncStoreService).manifest(null); } + getSynchronizer(source: SyncResource): IUserDataSynchroniser { + return (this.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getEnabledSynchronizers().find(s => s.resource === source)!; + } + } const ALL_SERVER_RESOURCES: ServerResource[] = [...ALL_SYNC_RESOURCES, 'machines']; diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index fbc37653ebbca..70bdb7fe22b17 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -9,7 +9,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { IUserDataSyncService, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncEnablementService, IUserDataSyncService, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; suite('UserDataSyncService', () => { @@ -49,6 +49,35 @@ suite('UserDataSyncService', () => { }); + test('test first time sync ever when a sync resource is disabled', async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(SyncResource.Settings, false); + const testObject = client.instantiationService.get(IUserDataSyncService); + + // Sync for first time + await (await testObject.createSyncTask(null)).run(); + + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Keybindings + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '0' } }, + // Snippets + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '0' } }, + // Global state + { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } }, + // Extensions + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + ]); + + }); + test('test first time sync ever with no data', async () => { // Setup the client const target = new UserDataSyncTestServer(); @@ -195,6 +224,40 @@ suite('UserDataSyncService', () => { ]); }); + test('test sync when there are local changes and sync resource is disabled', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncService); + await (await testObject.createSyncTask(null)).run(); + target.reset(); + + // Do changes in the client + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); + await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); + await fileService.writeFile(joinPath(environmentService.snippetsHome, 'html.json'), VSBuffer.fromString(`{}`)); + await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); + client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(SyncResource.Snippets, false); + + // Sync from the client + await (await testObject.createSyncTask(null)).run(); + + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Settings + { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, + // Keybindings + { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } }, + // Global state + { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } }, + ]); + }); + test('test sync when there are remote changes', async () => { const target = new UserDataSyncTestServer(); diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 4d1868c7536c0..e8803d037a0b9 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -354,10 +354,6 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private async syncBeforeTurningOn(title: string, manualSyncTask: IManualSyncTask): Promise { - - /* Make sure sync started on clean local state */ - await this.userDataSyncService.resetLocal(); - try { let action: FirstTimeSyncAction = 'manual'; From c2f4daf348e8cc1cc311d7ed9fcd9908cce933a2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 7 Dec 2021 16:15:13 -0800 Subject: [PATCH 0389/2210] Update webview bits version for tests --- .../vscode-api-tests/src/singlefolder-tests/webview.test.ts | 2 +- .../vscode-api-tests/src/singlefolder-tests/window.test.ts | 2 +- product.json | 2 +- .../services/environment/browser/environmentService.ts | 2 +- test/integration/browser/src/index.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 69ac05f3d3c52..86d3c8c22f368 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -18,7 +18,7 @@ function workspaceFile(...segments: string[]) { const testDocument = workspaceFile('bower.json'); -suite.skip('vscode API - webview', () => { +suite('vscode API - webview', () => { const disposables: vscode.Disposable[] = []; function _register(disposable: T) { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index f92f01e3abc8f..165773c832af5 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -371,7 +371,7 @@ suite('vscode API - window', () => { }); //#region Tabs API tests - test.skip('Tabs - Ensure tabs getter is correct', async () => { + test('Tabs - Ensure tabs getter is correct', async () => { const [docA, docB, docC, notebookDoc] = await Promise.all([ workspace.openTextDocument(await createRandomFile()), workspace.openTextDocument(await createRandomFile()), diff --git a/product.json b/product.json index 22e93d4b7c33b..96f6f454937e9 100644 --- a/product.json +++ b/product.json @@ -25,7 +25,7 @@ "licenseFileName": "LICENSE.txt", "reportIssueUrl": "https://github.com/microsoft/vscode/issues/new", "urlProtocol": "code-oss", - "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-webview.net/insider/c42793d0357ff9c6589cce79a847177fd42852ee/out/vs/workbench/contrib/webview/browser/pre/", + "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-webview.net/insider/9acd320edad7cea2c062d339fa04822c5eeb9e1d/out/vs/workbench/contrib/webview/browser/pre/", "extensionAllowedProposedApi": [ "ms-vscode.vscode-js-profile-flame", "ms-vscode.vscode-js-profile-table", diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index da8372b16f7a9..3d610ff5e49af 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -235,7 +235,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment const webviewExternalEndpointCommit = this.payload?.get('webviewExternalEndpointCommit'); return endpoint - .replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? 'c42793d0357ff9c6589cce79a847177fd42852ee') + .replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? '9acd320edad7cea2c062d339fa04822c5eeb9e1d') .replace('{{quality}}', (webviewExternalEndpointCommit ? 'insider' : this.productService.quality) ?? 'insider'); } diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 9375b10ffc888..262faad5f0645 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -65,7 +65,7 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true }); const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true }); - const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","c42793d0357ff9c6589cce79a847177fd42852ee"],["skipWelcome","true"]]`; + const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","9acd320edad7cea2c062d339fa04822c5eeb9e1d"],["skipWelcome","true"]]`; if (path.extname(testWorkspaceUri) === '.code-workspace') { await page.goto(`${endpoint.href}&workspace=${testWorkspaceUri}&payload=${payloadParam}`); From 3422499170d31fe1b0eb5ef7d2b61520a3afd888 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 7 Dec 2021 16:17:40 -0800 Subject: [PATCH 0390/2210] Force focus cell editor when needed Fix #138544 --- .../contrib/notebook/browser/notebookEditorWidget.ts | 12 +++++++++++- .../notebook/browser/view/renderers/cellRenderer.ts | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 38080f694654f..8b1b4fbfb9bac 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -306,7 +306,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _listViewInfoAccessor!: ListViewInfoAccessor; private _dndController: CellDragAndDropController | null = null; private _listTopCellToolbar: ListTopCellToolbar | null = null; - private _renderedEditors: Map = new Map(); + private _renderedEditors: Map = new Map(); private _viewContext: ViewContext; private _notebookViewModel: NotebookViewModel | undefined; private _localStore: DisposableStore = this._register(new DisposableStore()); @@ -1765,6 +1765,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD if (element && element.focusMode === CellFocusMode.Editor) { element.updateEditState(CellEditState.Editing, 'editorWidget.focus'); element.focusMode = CellFocusMode.Editor; + this.focusEditor(element); return; } } @@ -1773,6 +1774,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } } + private focusEditor(activeElement: CellViewModel): void { + for (const [element, editor] of this._renderedEditors.entries()) { + if (element === activeElement) { + editor.focus(); + return; + } + } + } + focusContainer() { if (this._webviewFocused) { this._webview?.focusWebview(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index cc48db29118e0..d570719eb52cf 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -127,7 +127,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen constructor( notebookEditor: INotebookEditorDelegate, dndController: CellDragAndDropController, - private renderedEditors: Map, + private renderedEditors: Map, contextKeyServiceProvider: (container: HTMLElement) => IContextKeyService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, @@ -367,7 +367,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende constructor( notebookEditor: INotebookEditorDelegate, - private renderedEditors: Map, + private renderedEditors: Map, dndController: CellDragAndDropController, contextKeyServiceProvider: (container: HTMLElement) => IContextKeyService, @IConfigurationService private configurationService: IConfigurationService, From a3e60d2dffe4a253723e7f92f521ce1ec43154e4 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 7 Dec 2021 16:22:54 -0800 Subject: [PATCH 0391/2210] Add messages for rejected promises --- extensions/github-authentication/src/common/utils.ts | 2 +- .../services/authentication/browser/authenticationService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/github-authentication/src/common/utils.ts b/extensions/github-authentication/src/common/utils.ts index e2af2e2d3c949..9ffbddec0b8c1 100644 --- a/extensions/github-authentication/src/common/utils.ts +++ b/extensions/github-authentication/src/common/utils.ts @@ -54,7 +54,7 @@ export function promiseFromEvent( let cancel = new EventEmitter(); return { promise: new Promise((resolve, reject) => { - cancel.event(_ => reject()); + cancel.event(_ => reject('Cancelled')); subscription = event((value: T) => { try { Promise.resolve(adapter(value, resolve, reject)) diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 1d81ce77b1028..48b6fa07b3312 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -721,7 +721,7 @@ export class AuthenticationService extends Disposable implements IAuthentication const didTimeout: Promise = new Promise((_, reject) => { setTimeout(() => { - reject(); + reject('Timed out waiting for authentication provider to register'); }, 5000); }); From d99e352bb628b144f24576a741c228c2b2c91a76 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 3 Dec 2021 12:43:09 -0800 Subject: [PATCH 0392/2210] initial attempt --- src/vs/base/browser/ui/list/listView.ts | 5 ++++- .../notebook/browser/notebookEditorWidget.ts | 15 ++++++++++++--- .../notebook/browser/view/notebookCellList.ts | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 5f767fad029ce..fe96e0a89ca2d 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -399,7 +399,7 @@ export class ListView implements ISpliceable, IDisposable { this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent); } - updateElementHeight(index: number, size: number | undefined, anchorIndex: number | null): void { + updateElementHeight(index: number, size: number | undefined, anchorIndex: number | null, callback?: () => void): void { if (index < 0 || index >= this.items.length) { return; } @@ -441,6 +441,9 @@ export class ListView implements ISpliceable, IDisposable { this.items[index].size = size; this.render(lastRenderRange, Math.max(0, this.lastRenderTop + heightDiff), this.lastRenderHeight, undefined, undefined, true); + if (callback) { + callback(); + } this.setScrollTop(this.lastRenderTop); this.eventuallyUpdateScrollDimensions(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 8b1b4fbfb9bac..c93287f90ea68 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2386,7 +2386,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this.notebookRendererMessaging.prepare(output.renderer.id); } - const cellTop = this._list.getAbsoluteTopOfElement(cell); + const webviewTop = parseInt((this._list.rowsContainer.firstChild as HTMLElement).style.top, 10); + const top = !!webviewTop ? (0 - webviewTop) : 0; + + const cellTop = this._list.getAbsoluteTopOfElement(cell) + top; + + // const cellTop = this._list.getAbsoluteTopOfElement(cell); if (!this._webview.insetMapping.has(output.source)) { await this._webview.createOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset); } else { @@ -2470,6 +2475,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const scrollHeight = this._list.scrollHeight; this._webview!.element.style.height = `${scrollHeight}px`; + const webviewTop = parseInt((this._list.rowsContainer.firstChild as HTMLElement).style.top, 10); + const top = !!webviewTop ? (0 - webviewTop) : 0; + const updateItems: IDisplayOutputLayoutUpdateRequest[] = []; const removedItems: ICellOutputViewModel[] = []; this._webview?.insetMapping.forEach((value, key) => { @@ -2496,7 +2504,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD updateItems.push({ cell, output: key, - cellTop, + cellTop: cellTop + top, outputOffset, forceDisplay: false, }); @@ -2509,7 +2517,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const cell = this.viewModel?.viewCells.find(cell => cell.id === cellId); if (cell) { const cellTop = this._list.getAbsoluteTopOfElement(cell); - markdownUpdateItems.push({ id: cellId, top: cellTop }); + // markdownUpdateItems.push({ id: cellId, top: cellTop }); + markdownUpdateItems.push({ id: cellId, top: cellTop + top }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index b371fac872dd9..b424b29615c14 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -883,6 +883,20 @@ export class NotebookCellList extends WorkbenchList implements ID return; } + if (index < this.firstVisibleIndex) { + // update element above viewport + const oldHeight = this.elementHeight(element); + console.log('size update', oldHeight, size); + const delta = oldHeight - size; + const date = new Date(); + this.view.updateElementHeight(index, size, null, () => { + console.log('update webview top', date.getSeconds(), date.getMilliseconds(), `-${delta}px`); + (this.rowsContainer.firstChild as HTMLElement).style.top = `-${delta}px`; + // should also extend webview element size + }); + return; + } + const focused = this.getFocus(); if (!focused.length) { this.view.updateElementHeight(index, size, null); From 1338ed5070d8a0f6866e9a17166644233edadb0c Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 3 Dec 2021 15:12:44 -0800 Subject: [PATCH 0393/2210] avoid tweaking list view. --- src/vs/base/browser/ui/list/listView.ts | 5 +---- .../contrib/notebook/browser/notebookEditorWidget.ts | 1 + .../contrib/notebook/browser/view/notebookCellList.ts | 11 +++++------ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index fe96e0a89ca2d..5f767fad029ce 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -399,7 +399,7 @@ export class ListView implements ISpliceable, IDisposable { this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent); } - updateElementHeight(index: number, size: number | undefined, anchorIndex: number | null, callback?: () => void): void { + updateElementHeight(index: number, size: number | undefined, anchorIndex: number | null): void { if (index < 0 || index >= this.items.length) { return; } @@ -441,9 +441,6 @@ export class ListView implements ISpliceable, IDisposable { this.items[index].size = size; this.render(lastRenderRange, Math.max(0, this.lastRenderTop + heightDiff), this.lastRenderHeight, undefined, undefined, true); - if (callback) { - callback(); - } this.setScrollTop(this.lastRenderTop); this.eventuallyUpdateScrollDimensions(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index c93287f90ea68..4fd485dcecf43 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1332,6 +1332,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }, id, resource, this._notebookOptions.computeWebviewOptions(), this.notebookRendererMessaging.getScoped(this._uuid)); this._webview.element.style.width = '100%'; + this._webview.element.style.top = '0'; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index b424b29615c14..93c8eb7bab672 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -886,14 +886,13 @@ export class NotebookCellList extends WorkbenchList implements ID if (index < this.firstVisibleIndex) { // update element above viewport const oldHeight = this.elementHeight(element); - console.log('size update', oldHeight, size); const delta = oldHeight - size; - const date = new Date(); - this.view.updateElementHeight(index, size, null, () => { - console.log('update webview top', date.getSeconds(), date.getMilliseconds(), `-${delta}px`); - (this.rowsContainer.firstChild as HTMLElement).style.top = `-${delta}px`; - // should also extend webview element size + // const date = new Date(); + Event.once(this.view.onWillScroll)(() => { + const webviewTop = parseInt((this.rowsContainer.firstChild as HTMLElement).style.top, 10); + (this.rowsContainer.firstChild as HTMLElement).style.top = `${webviewTop - delta}px`; }); + this.view.updateElementHeight(index, size, null); return; } From 9610a777908426efa78872f8f9ac2d18f34252fd Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 6 Dec 2021 14:22:47 -0800 Subject: [PATCH 0394/2210] fast dom node for webview element in nb. --- .../notebook/browser/notebookEditorWidget.ts | 14 +++++++++--- .../notebook/browser/view/notebookCellList.ts | 22 +++++++++++++++---- .../browser/view/notebookRenderingCommon.ts | 2 ++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 4fd485dcecf43..31c9cb103f772 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1335,7 +1335,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._webview.element.style.top = '0'; // attach the webview container to the DOM tree first - this._list.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); + this._list.attachWebview(this._webview.element); } private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) { @@ -2383,11 +2383,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return; } + if (!this._list.webviewElement) { + return; + } + if (output.type === RenderOutputType.Extension) { this.notebookRendererMessaging.prepare(output.renderer.id); } - const webviewTop = parseInt((this._list.rowsContainer.firstChild as HTMLElement).style.top, 10); + const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10); const top = !!webviewTop ? (0 - webviewTop) : 0; const cellTop = this._list.getAbsoluteTopOfElement(cell) + top; @@ -2473,10 +2477,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return; } + if (!this._list.webviewElement) { + return; + } + const scrollHeight = this._list.scrollHeight; this._webview!.element.style.height = `${scrollHeight}px`; - const webviewTop = parseInt((this._list.rowsContainer.firstChild as HTMLElement).style.top, 10); + const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10); const top = !!webviewTop ? (0 - webviewTop) : 0; const updateItems: IDisplayOutputLayoutUpdateRequest[] = []; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 93c8eb7bab672..41246f3c249ee 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -27,6 +27,7 @@ import { clamp } from 'vs/base/common/numbers'; import { ISplice } from 'vs/base/common/sequence'; import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { BaseCellRenderTemplate, INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; +import { FastDomNode } from 'vs/base/browser/fastDomNode'; export interface IFocusNextPreviousDelegate { onFocusNext(applyFocusNext: () => void): void; @@ -94,6 +95,12 @@ export class NotebookCellList extends WorkbenchList implements ID private readonly _viewContext: ViewContext; + private _webviewElement: FastDomNode | null = null; + + get webviewElement() { + return this._webviewElement; + } + constructor( private listUser: string, parentContainer: HTMLElement, @@ -232,6 +239,11 @@ export class NotebookCellList extends WorkbenchList implements ID })); } + attachWebview(element: HTMLElement) { + this.rowsContainer.insertAdjacentElement('afterbegin', element); + this._webviewElement = new FastDomNode(element); + } + elementAt(position: number): ICellViewModel | undefined { if (!this.view.length) { return undefined; @@ -888,10 +900,12 @@ export class NotebookCellList extends WorkbenchList implements ID const oldHeight = this.elementHeight(element); const delta = oldHeight - size; // const date = new Date(); - Event.once(this.view.onWillScroll)(() => { - const webviewTop = parseInt((this.rowsContainer.firstChild as HTMLElement).style.top, 10); - (this.rowsContainer.firstChild as HTMLElement).style.top = `${webviewTop - delta}px`; - }); + if (this._webviewElement) { + Event.once(this.view.onWillScroll)(() => { + const webviewTop = parseInt(this._webviewElement!.domNode.style.top, 10); + this._webviewElement!.setTop(webviewTop - delta); + }); + } this.view.updateElementHeight(index, size, null); return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 1ce50e25e9c63..13c290d0ba0cb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -29,6 +29,7 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; export interface INotebookCellList { isDisposed: boolean; viewModel: NotebookViewModel | null; + webviewElement: FastDomNode | null; readonly contextKeyService: IContextKeyService; element(index: number): ICellViewModel | undefined; elementAt(position: number): ICellViewModel | undefined; @@ -52,6 +53,7 @@ export interface INotebookCellList { readonly onContextMenu: Event>; detachViewModel(): void; attachViewModel(viewModel: NotebookViewModel): void; + attachWebview(element: HTMLElement): void; clear(): void; getViewIndex(cell: ICellViewModel): number | undefined; getViewIndex2(modelIndex: number): number | undefined; From b5c188232615fe6e06ecd08cca7fbdb07a7eab45 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 7 Dec 2021 11:14:56 -0800 Subject: [PATCH 0395/2210] Webview element fix up when it is beyond boundary. --- .../notebook/browser/notebookEditorWidget.ts | 5 ++--- .../notebook/browser/view/notebookCellList.ts | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 31c9cb103f772..b569db094ebd4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -44,7 +44,7 @@ import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/vie import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; -import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; +import { NotebookCellList, NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { BackLayerWebView, INotebookWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys'; @@ -1332,7 +1332,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }, id, resource, this._notebookOptions.computeWebviewOptions(), this.notebookRendererMessaging.getScoped(this._uuid)); this._webview.element.style.width = '100%'; - this._webview.element.style.top = '0'; // attach the webview container to the DOM tree first this._list.attachWebview(this._webview.element); @@ -2482,7 +2481,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } const scrollHeight = this._list.scrollHeight; - this._webview!.element.style.height = `${scrollHeight}px`; + this._webview!.element.style.height = `${scrollHeight + NOTEBOOK_WEBVIEW_BOUNDARY * 2}px`; const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10); const top = !!webviewTop ? (0 - webviewTop) : 0; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 41246f3c249ee..73680066afa14 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -38,6 +38,13 @@ export interface INotebookCellListOptions extends IWorkbenchListOptions= 0 && webviewTop <= NOTEBOOK_WEBVIEW_BOUNDARY * 2; +} + export class NotebookCellList extends WorkbenchList implements IDisposable, IStyleController, INotebookCellList { get onWillScroll(): Event { return this.view.onWillScroll; } @@ -240,6 +247,7 @@ export class NotebookCellList extends WorkbenchList implements ID } attachWebview(element: HTMLElement) { + element.style.top = `-${NOTEBOOK_WEBVIEW_BOUNDARY}px`; this.rowsContainer.insertAdjacentElement('afterbegin', element); this._webviewElement = new FastDomNode(element); } @@ -903,7 +911,16 @@ export class NotebookCellList extends WorkbenchList implements ID if (this._webviewElement) { Event.once(this.view.onWillScroll)(() => { const webviewTop = parseInt(this._webviewElement!.domNode.style.top, 10); - this._webviewElement!.setTop(webviewTop - delta); + if (validateWebviewBoundary(this._webviewElement!.domNode)) { + this._webviewElement!.setTop(webviewTop - delta); + } else { + // When the webview top boundary is below the list view scrollable element top boundary, then we can't insert a markdown cell at the top + // or when its bottom boundary is above the list view bottom boundary, then we can't insert a markdown cell at the end + // thus we have to revert the webview element position to initial state `-NOTEBOOK_WEBVIEW_BOUNDARY`. + // this will trigger one visual flicker (as we need to update element offsets in the webview) + // but as long as NOTEBOOK_WEBVIEW_BOUNDARY is large enough, it will happen less often + this._webviewElement!.setTop(-NOTEBOOK_WEBVIEW_BOUNDARY); + } }); } this.view.updateElementHeight(index, size, null); From 141fb55e0bd595ea0e61ba8f63565730222d929a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 7 Dec 2021 16:31:38 -0800 Subject: [PATCH 0396/2210] update flickering optimization doc. --- .../cell-resize-above-viewport.drawio.svg | 654 ++++++++++++++++++ .../notebook/browser/docs/notebook.layout.md | 16 + 2 files changed, 670 insertions(+) create mode 100644 src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg diff --git a/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg b/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg new file mode 100644 index 0000000000000..f546865a2a612 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + Notebook List View +
    +
    +
    +
    + + Notebook List View + +
    +
    + + + + +
    +
    +
    + Webview  top -1000 +
    +
    +
    +
    + + Webview  top -1000 + +
    +
    + + + + + + +
    +
    +
    + Viewport +
    +
    +
    +
    + + Viewport + +
    +
    + + + + + + + + + +
    +
    +
    + Notebook List View +
    +
    +
    +
    + + Notebook List View + +
    +
    + + + + + + + + + + + + + + + + +
    +
    +
    + Grow Height by 50 +
    +
    +
    +
    + + Grow Height by 50 + +
    +
    + + + + +
    +
    +
    + scrollTop 1000 +
    +
    +
    +
    + + scrollTop 1000 + +
    +
    + + + + +
    +
    +
    + scrollTop 1000 +
    +
    +
    +
    + + scrollTop 1000 + +
    +
    + + + + + + + + + + + + +
    +
    +
    + Notebook List View +
    +
    +
    +
    + + Notebook List View + +
    +
    + + + + + + + + + + + + + + + + +
    +
    +
    + scrollTop 1050 +
    +
    +
    +
    + + scrollTop 1050 + +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + Notebook List View +
    +
    +
    +
    + + Notebook List View + +
    +
    + + + + + + + + + + + + + + + + +
    +
    +
    + scrollTop 1050 +
    +
    +
    +
    + + scrollTop 1050 + +
    +
    + + + + + + +
    +
    +
    + Adjust top +
    +
    +
    +
    + + Adjust top + +
    +
    + + + + + + +
    +
    +
    + UpdateScrollTop +
    +
    +
    +
    + + UpdateScrollTop + +
    +
    + + + + + + +
    +
    +
    + Webview  top -1000 +
    +
    +
    +
    + + Webview  top -1000 + +
    +
    + + + + +
    +
    +
    + Webview  top -1000 +
    +
    +
    +
    + + Webview  top -1000 + +
    +
    + + + + +
    +
    +
    + Webview  top -1000 +
    +
    +
    +
    + + Webview  top -1000 + +
    +
    + + + + + + + + + + + + + + +
    +
    +
    + Notebook List View +
    +
    +
    +
    + + Notebook List View + +
    +
    + + + + + + + + + + + + + + + + +
    +
    +
    + scrollTop 1050 +
    +
    +
    +
    + + scrollTop 1050 + +
    +
    + + + + +
    +
    +
    + Webview  top -950 +
    +
    +
    +
    + + Webview  top -950 + +
    +
    + + + + + + + + + + + + + + +
    +
    +
    + Notebook List View +
    +
    +
    +
    + + Notebook List View + +
    +
    + + + + + + + + + + + + + + + + +
    +
    +
    + scrollTop 1050 +
    +
    +
    +
    + + scrollTop 1050 + +
    +
    + + + + +
    +
    +
    + Webview  top -950 +
    +
    +
    +
    + + Webview  top -950 + +
    +
    + + + + + + +
    +
    +
    + Adjust top +
    +
    +
    +
    + + Adjust top + +
    +
    + + + + + + + + +
    +
    +
    + 1 +
    +
    +
    +
    + + 1 + +
    +
    + + + + +
    +
    +
    + 2 +
    +
    +
    +
    + + 2 + +
    +
    + + + + +
    +
    +
    + 3 +
    +
    +
    +
    + + 3 + +
    +
    + + + + +
    +
    +
    + 4 +
    +
    +
    +
    + + 4 + +
    +
    + + + + +
    +
    +
    + 4' +
    +
    +
    +
    + + 4' + +
    +
    + + + + +
    +
    +
    + 5 +
    +
    +
    +
    + + 5 + +
    +
    +
    + + + + + Viewer does not support full SVG 1.1 + + + +
    diff --git a/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md index 7ca30321288a2..a26af5967bae1 100644 --- a/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md +++ b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md @@ -9,6 +9,7 @@ The notebook editor is a virtualized list view rendered in two contexts (mainfra * [Executing code cell followed by markdown cells](#executing-code-cell-followed-by-markdown-cells) * [Re-executing code cell followed by markdown cells](#re-executing-code-cell-followed-by-markdown-cells) * [Scrolling](#scrolling) + * [Avoid flickering on resize of cells above current viewport](#avoid-flickering-on-resize-of-cells-above-current-viewport) # Architecture @@ -131,6 +132,21 @@ Since most elements' positions are absoulte and there is latency between the two While we continue optimizing the layout code, we need to make sure that the new optimization won't lead to regression in above three aspects. Here is a list of existing optimziations we already have and we want to make sure they still perform well when updating layout code. +## Avoid flickering on resize of cells above current viewport + +We always ensure that elements in current viewport are stable (their visual positions don't change) when cells above current viewport resize. Resizing a cell above viewport will then include following steps as shown in below diagram + +![cell resize above viewport](./cell-resize-above-viewport.drawio.svg) + +1. Users scroll to the middle of the document, with one markdown cell partially visible (blue box in the green container) and one code cell full visible (blue box in the white container) +2. The code cell above current viewport grows by 50px. The list view will then push down every cell below it. Thus the code cell in the viewport will move down by 50px. In current tick/frame, the markdown preview elemnets in the webview are not updated yet thus it's still partially visible. +3. To ensure the code cell's position is stable, we would move the whole list view upwards by 50px. The code cell's position is now fixed but at the same time, the webview also moves up by 50px (as it's a child of the list view scrollable element). +4. Lastly we sent requests to the webview to fix the visual positions of markdown previews + +After the last step, both the code and markdown cells in the viewport stays where they were. However due to the fact that code cells and markdown cells are rendered in two different contexts (UI and iframe), there is always latency between step 3 and 4 so users will notice annoying flickering of markdown cells easily. + +The fix is kidn of "tricky". Instead of adjusting the position of the partially markdown cells, we can actually make it visually stable by adjusting the position of the webview element (step 4'). That would mess up the positions of markdown cells above current viewport, but we can fix them in next frame/tick (step 5) and since they are never in the viewport, users won't notice their position flicker/shift. + ## Executing code cell followed by markdown cells Code cell outputs and markdown cells are both rendered in the underling webview. When executing a code cell, the list view will From 6739b4fd7c434d35a243ce92a2c9ff0e4ee06fdd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Dec 2021 01:32:42 +0100 Subject: [PATCH 0397/2210] Update filter when programmatically changing quick input value (#137857) * Update filter when programmatically changing quick input value * only filter when quick pick is visible --- .../parts/quickinput/browser/quickInput.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index fe76816a6e544..eae311302446b 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -487,9 +487,21 @@ class QuickPick extends QuickInput implements IQuickPi } set value(value: string) { + this.doSetValue(value); + } + + private doSetValue(value: string, skipUpdate?: boolean): void { if (this._value !== value) { - this._value = value || ''; - this.update(); + this._value = value; + if (!skipUpdate) { + this.update(); + } + if (this.visible) { + const didFilter = this.ui.list.filter(this.filterValue(this._value)); + if (didFilter) { + this.trySelectFirst(); + } + } this.onDidChangeValueEmitter.fire(this._value); } } @@ -734,15 +746,7 @@ class QuickPick extends QuickInput implements IQuickPi if (!this.visible) { this.visibleDisposables.add( this.ui.inputBox.onDidChange(value => { - if (value === this.value) { - return; - } - this._value = value; - const didFilter = this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); - if (didFilter) { - this.trySelectFirst(); - } - this.onDidChangeValueEmitter.fire(value); + this.doSetValue(value, true /* skip update since this originates from the UI */); })); this.visibleDisposables.add(this.ui.inputBox.onMouseDown(event => { if (!this.autoFocusOnList) { From 46421dda2536b09fc328aedd20837b7084f92dcc Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 01:29:31 +0100 Subject: [PATCH 0398/2210] #138511 simplify check if remote has changed and use throttled delayer for triggering local change --- .../userDataSync/common/abstractSynchronizer.ts | 11 ++++++----- .../platform/userDataSync/common/extensionsSync.ts | 9 +++++++++ .../userDataSync/common/globalStateSync.ts | 11 +++++++++++ .../userDataSync/common/keybindingsSync.ts | 13 +++++++++++++ .../platform/userDataSync/common/settingsSync.ts | 14 ++++++++++++++ .../platform/userDataSync/common/snippetsSync.ts | 11 +++++++++++ .../userDataSync/test/common/synchronizer.test.ts | 4 ++++ 7 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 1ce5767d00010..1469f889cb8c1 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { equals } from 'vs/base/common/arrays'; -import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -107,7 +107,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa private _onDidChangeConflicts: Emitter = this._register(new Emitter()); readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - private readonly localChangeTriggerScheduler = new RunOnceScheduler(() => this.doTriggerLocalChange(), 50); + private readonly localChangeTriggerThrottler = new ThrottledDelayer(50); private readonly _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -139,8 +139,8 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService); } - protected async triggerLocalChange(): Promise { - this.localChangeTriggerScheduler.schedule(); + protected triggerLocalChange(): void { + this.localChangeTriggerThrottler.trigger(() => this.doTriggerLocalChange()); } protected async doTriggerLocalChange(): Promise { @@ -158,7 +158,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa else { this.logService.trace(`${this.syncResourceLogLabel}: Checking for local changes...`); const lastSyncUserData = await this.getLastSyncUserData(); - const hasRemoteChanged = lastSyncUserData ? (await this.doGenerateSyncResourcePreview(lastSyncUserData, lastSyncUserData, true, this.getUserDataSyncConfiguration(), CancellationToken.None)).resourcePreviews.some(({ remoteChange }) => remoteChange !== Change.None) : true; + const hasRemoteChanged = lastSyncUserData ? await this.hasRemoteChanged(lastSyncUserData) : true; if (hasRemoteChanged) { this._onDidChangeLocal.fire(); } @@ -710,6 +710,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa protected abstract getMergeResult(resourcePreview: IResourcePreview, token: CancellationToken): Promise; protected abstract getAcceptResult(resourcePreview: IResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise; protected abstract applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, result: [IResourcePreview, IAcceptResult][], force: boolean): Promise; + protected abstract hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise; abstract hasLocalData(): Promise; abstract getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]>; diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d63631406616a..f1035a65444bb 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -158,6 +158,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse }]; } + protected async hasRemoteChanged(lastSyncUserData: ILastSyncUserData): Promise { + const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null; + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, true); + const localExtensions = this.getLocalExtensions(installedExtensions); + const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); + const { remote } = merge(localExtensions, lastSyncExtensions, lastSyncExtensions, lastSyncUserData.skippedExtensions || [], ignoredExtensions); + return remote !== null; + } + private getPreviewContent(localExtensions: ISyncExtension[], added: ISyncExtension[], updated: ISyncExtension[], removed: IExtensionIdentifier[]): string { const preview: ISyncExtension[] = [...added, ...updated]; diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index bc01d952b10ab..7ed81f6f97bff 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -137,6 +137,17 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs }]; } + protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise { + const lastSyncGlobalState: IGlobalState | null = lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; + if (lastSyncGlobalState === null) { + return true; + } + const localGlobalState = await this.getLocalGlobalState(); + const storageKeys = this.getStorageKeys(lastSyncGlobalState); + const { remote } = merge(localGlobalState.storage, lastSyncGlobalState.storage, lastSyncGlobalState.storage, storageKeys, this.logService); + return remote !== null; + } + protected async getMergeResult(resourcePreview: IGlobalStateResourcePreview, token: CancellationToken): Promise { return { ...resourcePreview.previewResult, hasConflicts: false }; } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 2272e7153ea3c..8c8dd86f0d95a 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -154,6 +154,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } + protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise { + const lastSyncContent = this.getKeybindingsContentFromLastSyncUserData(lastSyncUserData); + if (lastSyncContent === null) { + return true; + } + + const fileContent = await this.getLocalFileContent(); + const localContent: string = fileContent ? fileContent.value.toString() : ''; + const formattingOptions = await this.getFormattingOptions(); + const result = await merge(localContent || '[]', lastSyncContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); + return result.hasConflicts || result.mergeContent !== lastSyncContent; + } + protected async getMergeResult(resourcePreview: IKeybindingsResourcePreview, token: CancellationToken): Promise { return resourcePreview.previewResult; } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 9de908f8fbb67..5eff7eb26eb12 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -136,6 +136,20 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement }]; } + protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise { + const lastSettingsSyncContent: ISettingsSyncContent | null = this.getSettingsSyncContent(lastSyncUserData); + if (lastSettingsSyncContent === null) { + return true; + } + + const fileContent = await this.getLocalFileContent(); + const localContent: string = fileContent ? fileContent.value.toString().trim() : ''; + const ignoredSettings = await this.getIgnoredSettings(); + const formattingOptions = await this.getFormattingOptions(); + const result = merge(localContent || '{}', lastSettingsSyncContent.settings, lastSettingsSyncContent.settings, ignoredSettings, [], formattingOptions); + return result.remoteContent !== null; + } + protected async getMergeResult(resourcePreview: ISettingsResourcePreview, token: CancellationToken): Promise { const formatUtils = await this.getFormattingOptions(); const ignoredSettings = await this.getIgnoredSettings(); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index fddc79a806818..483ed74f4e95b 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -70,6 +70,17 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD return this.getResourcePreviews(mergeResult, local, remoteSnippets || {}); } + protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise { + const lastSyncSnippets: IStringDictionary | null = lastSyncUserData.syncData ? this.parseSnippets(lastSyncUserData.syncData) : null; + if (lastSyncSnippets === null) { + return true; + } + const local = await this.getSnippetsFileContents(); + const localSnippets = this.toSnippetsContents(local); + const mergeResult = merge(localSnippets, lastSyncSnippets, lastSyncSnippets); + return Object.keys(mergeResult.remote.added).length > 0 || Object.keys(mergeResult.remote.updated).length > 0 || mergeResult.remote.removed.length > 0 || mergeResult.conflicts.length > 0; + } + protected async getMergeResult(resourcePreview: ISnippetsResourcePreview, token: CancellationToken): Promise { return resourcePreview.previewResult; } diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 2e0a16e806e0f..01b44d101f384 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -76,6 +76,10 @@ class TestSynchroniser extends AbstractSynchroniser { }]; } + protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise { + return true; + } + protected async getMergeResult(resourcePreview: ITestResourcePreview, token: CancellationToken): Promise { return { content: resourcePreview.ref, From ab76ef366666cf80926b17c899a639d17ab3d356 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 7 Dec 2021 16:42:01 -0800 Subject: [PATCH 0399/2210] fix unicode --- .../cell-resize-above-viewport.drawio.svg | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg b/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg index f546865a2a612..909b8665213bc 100644 --- a/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg +++ b/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg @@ -48,13 +48,13 @@
    - Webview  top -1000 + Webview top -1000
    - Webview  top -1000 + Webview top -1000 @@ -327,13 +327,13 @@
    - Webview  top -1000 + Webview top -1000
    - Webview  top -1000 + Webview top -1000 @@ -344,13 +344,13 @@
    - Webview  top -1000 + Webview top -1000
    - Webview  top -1000 + Webview top -1000 @@ -361,13 +361,13 @@
    - Webview  top -1000 + Webview top -1000
    - Webview  top -1000 + Webview top -1000 @@ -434,13 +434,13 @@
    - Webview  top -950 + Webview top -950
    - Webview  top -950 + Webview top -950 @@ -507,13 +507,13 @@
    - Webview  top -950 + Webview top -950
    - Webview  top -950 + Webview top -950 From 1d996470961dd59e5b69667022ef788fec9e7125 Mon Sep 17 00:00:00 2001 From: Guy Barnard <1922295+guybarnard@users.noreply.github.com> Date: Tue, 7 Dec 2021 18:56:51 -0600 Subject: [PATCH 0400/2210] Update webview service-worker invocation (#138258) Update webview service-worker invocation to remove unnecessary variables in the query string that change on every invocation. This causes a lengthy delay in certain browsers. This affects built in webviews such as displaying image files as well as custom editor extensions. --- src/vs/workbench/contrib/webview/browser/pre/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 3a36f461de159..c7edd335cd13f 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -204,7 +204,7 @@ const workerReady = new Promise((resolve, reject) => { return reject(new Error('Service Workers are not enabled. Webviews will not work. Try disabling private/incognito mode.')); } - const swPath = `service-worker.js${self.location.search}`; + const swPath = `service-worker.js?vscode-resource-base-authority=${(new URL(location.toString()).searchParams).get('vscode-resource-base-authority')}`; navigator.serviceWorker.register(swPath).then( async registration => { From 35a17b1397e5b934f723991f35091ee2ae7f1022 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 7 Dec 2021 16:58:12 -0800 Subject: [PATCH 0401/2210] :lipstick: --- .../notebook/test/browser/notebookCellList.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts index 0e0af31e62c38..4fbead69d3c82 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts @@ -234,14 +234,14 @@ suite('NotebookCellList', () => { assert.deepStrictEqual(cellList.scrollTop, 55); assert.deepStrictEqual(cellList.getViewScrollBottom(), 265); - cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); - assert.deepStrictEqual(cellList.scrollTop, 5); - assert.deepStrictEqual(cellList.getViewScrollBottom(), 215); + // cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); + // assert.deepStrictEqual(cellList.scrollTop, 5); + // assert.deepStrictEqual(cellList.getViewScrollBottom(), 215); // focus won't be visible after cell 0 grow to 250, so let's try to keep the focused cell visible - cellList.updateElementHeight2(viewModel.cellAt(0)!, 250); - assert.deepStrictEqual(cellList.scrollTop, 250 + 100 - cellList.renderHeight); - assert.deepStrictEqual(cellList.getViewScrollBottom(), 250 + 100 - cellList.renderHeight + 210); + // cellList.updateElementHeight2(viewModel.cellAt(0)!, 250); + // assert.deepStrictEqual(cellList.scrollTop, 250 + 100 - cellList.renderHeight); + // assert.deepStrictEqual(cellList.getViewScrollBottom(), 250 + 100 - cellList.renderHeight + 210); }); }); From 10cdcbbe139a7a0f87d343eb62e7c2106e1098f8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 02:19:17 +0100 Subject: [PATCH 0402/2210] #138511 format only while displaying --- .../userDataSync/common/extensionsSync.ts | 17 +++++++++-------- .../userDataSync/common/globalStateSync.ts | 14 +++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index f1035a65444bb..b4c5f829d9946 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -146,10 +146,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return [{ skippedExtensions, localResource: this.localResource, - localContent: this.stringify(localExtensions), + localContent: this.stringify(localExtensions, false), localExtensions, remoteResource: this.remoteResource, - remoteContent: remoteExtensions ? this.stringify(remoteExtensions) : null, + remoteContent: remoteExtensions ? this.stringify(remoteExtensions, false) : null, previewResource: this.previewResource, previewResult, localChange: previewResult.localChange, @@ -188,7 +188,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse preview.push(localExtension); } - return this.stringify(preview); + return this.stringify(preview, false); } protected async getMergeResult(resourcePreview: IExtensionResourcePreview, token: CancellationToken): Promise { @@ -292,11 +292,12 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const installedExtensions = await this.extensionManagementService.getInstalled(); const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier))); - return this.stringify(localExtensions); + return this.stringify(localExtensions, true); } if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { - return this.resolvePreviewContent(uri); + const content = await this.resolvePreviewContent(uri); + return content ? this.stringify(JSON.parse(content), true) : content; } let content = await super.resolveContent(uri); @@ -310,7 +311,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (syncData) { switch (this.extUri.basename(uri)) { case 'extensions.json': - return this.stringify(this.parseExtensions(syncData)); + return this.stringify(this.parseExtensions(syncData), true); } } } @@ -318,7 +319,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return null; } - private stringify(extensions: ISyncExtension[]): string { + private stringify(extensions: ISyncExtension[], format: boolean): string { extensions.sort((e1, e2) => { if (!e1.identifier.uuid && e2.identifier.uuid) { return -1; @@ -328,7 +329,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } return compare(e1.identifier.id, e2.identifier.id); }); - return JSON.stringify(extensions, null, '\t'); + return format ? JSON.stringify(extensions, null, '\t') : JSON.stringify(extensions); } async hasLocalData(): Promise { diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 7ed81f6f97bff..d6791f0523d91 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -43,12 +43,12 @@ export interface IGlobalStateResourcePreview extends IResourcePreview { readonly storageKeys: StorageKeys; } -function stringify(globalState: IGlobalState): string { +function stringify(globalState: IGlobalState, format: boolean): string { const storageKeys = globalState.storage ? Object.keys(globalState.storage).sort() : []; const storage: IStringDictionary = {}; storageKeys.forEach(key => storage[key] = globalState.storage[key]); globalState.storage = storage; - return JSON.stringify(globalState, null, '\t'); + return format ? JSON.stringify(globalState, null, '\t') : JSON.stringify(globalState); } const GLOBAL_STATE_DATA_VERSION = 1; @@ -124,10 +124,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return [{ localResource: this.localResource, - localContent: stringify(localGlobalState), + localContent: stringify(localGlobalState, false), localUserData: localGlobalState, remoteResource: this.remoteResource, - remoteContent: remoteGlobalState ? stringify(remoteGlobalState) : null, + remoteContent: remoteGlobalState ? stringify(remoteGlobalState, false) : null, previewResource: this.previewResource, previewResult, localChange: previewResult.localChange, @@ -243,7 +243,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs override async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) { const localGlobalState = await this.getLocalGlobalState(); - return stringify(localGlobalState); + return stringify(localGlobalState, true); } if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { @@ -261,7 +261,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (syncData) { switch (this.extUri.basename(uri)) { case 'globalState.json': - return stringify(JSON.parse(syncData.content)); + return stringify(JSON.parse(syncData.content), true); } } } @@ -474,7 +474,7 @@ export class UserDataSyncStoreTypeSynchronizer { // Write the global state to remote const machineId = await getServiceMachineId(this.environmentService, this.fileService, this.storageService); - const syncDataToUpdate: ISyncData = { version: GLOBAL_STATE_DATA_VERSION, machineId, content: stringify(remoteGlobalState) }; + const syncDataToUpdate: ISyncData = { version: GLOBAL_STATE_DATA_VERSION, machineId, content: stringify(remoteGlobalState, false) }; await this.userDataSyncStoreClient.write(SyncResource.GlobalState, JSON.stringify(syncDataToUpdate), globalStateUserData.ref, syncHeaders); } From 2a6a8c59b31f740561b5792bd0846f023098db27 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 02:20:54 +0100 Subject: [PATCH 0403/2210] #138511 format only while displaying --- src/vs/platform/userDataSync/common/globalStateSync.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index d6791f0523d91..623e2b3dd7738 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -247,7 +247,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { - return this.resolvePreviewContent(uri); + const content = await this.resolvePreviewContent(uri); + return content ? stringify(JSON.parse(content), true) : content; } let content = await super.resolveContent(uri); From 530480dcaa227f7c5a8442f47042f4c17f6035fc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Dec 2021 08:02:47 +0100 Subject: [PATCH 0404/2210] lifecycle - add tests for shutdown events --- .../electron-sandbox/lifecycleService.ts | 4 +- .../electron-browser/lifecycleService.test.ts | 158 ++++++++++++++++++ .../workingCopyBackupTracker.test.ts | 12 +- 3 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 src/vs/workbench/services/lifecycle/test/electron-browser/lifecycleService.test.ts diff --git a/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts index c4ace02d569cd..16df747217819 100644 --- a/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-sandbox/lifecycleService.ts @@ -70,7 +70,7 @@ export class NativeLifecycleService extends AbstractLifecycleService { }); } - private async handleBeforeShutdown(reason: ShutdownReason): Promise { + protected async handleBeforeShutdown(reason: ShutdownReason): Promise { const logService = this.logService; const vetos: (boolean | Promise)[] = []; @@ -149,7 +149,7 @@ export class NativeLifecycleService extends AbstractLifecycleService { this._onBeforeShutdownError.fire({ reason, error }); } - private async handleWillShutdown(reason: ShutdownReason): Promise { + protected async handleWillShutdown(reason: ShutdownReason): Promise { const joiners: Promise[] = []; const pendingJoiners = new Set(); diff --git a/src/vs/workbench/services/lifecycle/test/electron-browser/lifecycleService.test.ts b/src/vs/workbench/services/lifecycle/test/electron-browser/lifecycleService.test.ts new file mode 100644 index 0000000000000..63cd21065378d --- /dev/null +++ b/src/vs/workbench/services/lifecycle/test/electron-browser/lifecycleService.test.ts @@ -0,0 +1,158 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { NativeLifecycleService } from 'vs/workbench/services/lifecycle/electron-sandbox/lifecycleService'; +import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; + +suite('Lifecycleservice', function () { + + let lifecycleService: TestLifecycleService; + let disposables: DisposableStore; + + class TestLifecycleService extends NativeLifecycleService { + + override handleBeforeShutdown(reason: ShutdownReason): Promise { + return super.handleBeforeShutdown(reason); + } + + override handleWillShutdown(reason: ShutdownReason): Promise { + return super.handleWillShutdown(reason); + } + } + + setup(async () => { + disposables = new DisposableStore(); + + const instantiationService = workbenchInstantiationService(disposables); + lifecycleService = instantiationService.createInstance(TestLifecycleService); + }); + + teardown(async () => { + disposables.dispose(); + }); + + test('onBeforeShutdown - final veto called after other vetos', async function () { + let vetoCalled = false; + let finalVetoCalled = false; + + const order: number[] = []; + + lifecycleService.onBeforeShutdown(e => { + e.veto(new Promise(resolve => { + vetoCalled = true; + order.push(1); + + resolve(false); + }), 'test'); + }); + + lifecycleService.onBeforeShutdown(e => { + e.finalVeto(() => { + return new Promise(resolve => { + finalVetoCalled = true; + order.push(2); + + resolve(true); + }); + }, 'test'); + }); + + const veto = await lifecycleService.handleBeforeShutdown(ShutdownReason.QUIT); + + assert.strictEqual(veto, true); + assert.strictEqual(vetoCalled, true); + assert.strictEqual(finalVetoCalled, true); + assert.strictEqual(order[0], 1); + assert.strictEqual(order[1], 2); + }); + + test('onBeforeShutdown - final veto not called when veto happened before', async function () { + let vetoCalled = false; + let finalVetoCalled = false; + + lifecycleService.onBeforeShutdown(e => { + e.veto(new Promise(resolve => { + vetoCalled = true; + + resolve(true); + }), 'test'); + }); + + lifecycleService.onBeforeShutdown(e => { + e.finalVeto(() => { + return new Promise(resolve => { + finalVetoCalled = true; + + resolve(true); + }); + }, 'test'); + }); + + const veto = await lifecycleService.handleBeforeShutdown(ShutdownReason.QUIT); + + assert.strictEqual(veto, true); + assert.strictEqual(vetoCalled, true); + assert.strictEqual(finalVetoCalled, false); + }); + + test('onBeforeShutdown - veto with error is treated as veto', async function () { + lifecycleService.onBeforeShutdown(e => { + e.veto(new Promise((resolve, reject) => { + reject(new Error('Fail')); + }), 'test'); + }); + + const veto = await lifecycleService.handleBeforeShutdown(ShutdownReason.QUIT); + + assert.strictEqual(veto, true); + }); + + test('onBeforeShutdown - final veto with error is treated as veto', async function () { + lifecycleService.onBeforeShutdown(e => { + e.finalVeto(() => new Promise((resolve, reject) => { + reject(new Error('Fail')); + }), 'test'); + }); + + const veto = await lifecycleService.handleBeforeShutdown(ShutdownReason.QUIT); + + assert.strictEqual(veto, true); + }); + + test('onWillShutdown - join', async function () { + let joinCalled = false; + + lifecycleService.onWillShutdown(e => { + e.join(new Promise(resolve => { + joinCalled = true; + + resolve(); + }), 'test'); + }); + + await lifecycleService.handleWillShutdown(ShutdownReason.QUIT); + + assert.strictEqual(joinCalled, true); + }); + + test('onWillShutdown - join with error is handled', async function () { + let joinCalled = false; + + lifecycleService.onWillShutdown(e => { + e.join(new Promise((resolve, reject) => { + joinCalled = true; + + reject(new Error('Fail')); + }), 'test'); + }); + + await lifecycleService.handleWillShutdown(ShutdownReason.QUIT); + + assert.strictEqual(joinCalled, true); + }); +}); diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index 2f3e40da472e0..44575541afe51 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -89,9 +89,14 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { private readonly _onDidResume = this._register(new Emitter()); readonly onDidResume = this._onDidResume.event; + private readonly _onDidSuspend = this._register(new Emitter()); + readonly onDidSuspend = this._onDidSuspend.event; + protected override suspendBackupOperations(): { resume: () => void; } { const { resume } = super.suspendBackupOperations(); + this._onDidSuspend.fire(); + return { resume: () => { resume(); @@ -376,7 +381,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { await cleanup(); }); - test('onWillShutdown - pending backup operations canceled', async function () { + test('onWillShutdown - pending backup operations canceled and tracker suspended/resumsed', async function () { const { accessor, tracker, cleanup } = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -389,9 +394,14 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { assert.strictEqual(accessor.workingCopyService.dirtyCount, 1); assert.strictEqual(tracker.pendingBackupOperationCount, 1); + const onSuspend = Event.toPromise(tracker.onDidSuspend); + const event = new TestBeforeShutdownEvent(); + event.reason = ShutdownReason.QUIT; accessor.lifecycleService.fireBeforeShutdown(event); + await onSuspend; + assert.strictEqual(tracker.pendingBackupOperationCount, 0); // Ops are suspended during shutdown! From fa4e093030ce732577219dacbcf367ac9d995d9a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Dec 2021 08:42:22 +0100 Subject: [PATCH 0405/2210] backups - more shutdown tweaks --- .../browser/workingCopyBackupTracker.ts | 2 +- .../common/workingCopyBackupTracker.ts | 8 +-- .../workingCopyBackupTracker.ts | 59 ++++++++++--------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts index 54d2fd61b2e34..82c5d7a8b8376 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts @@ -29,7 +29,7 @@ export class BrowserWorkingCopyBackupTracker extends WorkingCopyBackupTracker im super(workingCopyBackupService, workingCopyService, logService, lifecycleService, filesConfigurationService, workingCopyEditorService, editorService, editorGroupService); } - protected onBeforeShutdown(reason: ShutdownReason): boolean { + protected onFinalBeforeShutdown(reason: ShutdownReason): boolean { // Web: we cannot perform long running in the shutdown phase // As such we need to check sync if there are any dirty working diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts index edee053cc6187..0e48c9abf7bff 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts @@ -57,14 +57,14 @@ export abstract class WorkingCopyBackupTracker extends Disposable { this._register(this.workingCopyService.onDidChangeContent(workingCopy => this.onDidChangeContent(workingCopy))); // Lifecycle - this.lifecycleService.onBeforeShutdown(event => (event as InternalBeforeShutdownEvent).finalVeto(() => this.onBeforeShutdown(event.reason), 'veto.backups')); + this.lifecycleService.onBeforeShutdown(event => (event as InternalBeforeShutdownEvent).finalVeto(() => this.onFinalBeforeShutdown(event.reason), 'veto.backups')); this.lifecycleService.onWillShutdown(() => this.onWillShutdown()); // Once a handler registers, restore backups this._register(this.workingCopyEditorService.onDidRegisterHandler(handler => this.restoreBackups(handler))); } - protected abstract onBeforeShutdown(reason: ShutdownReason): boolean | Promise; + protected abstract onFinalBeforeShutdown(reason: ShutdownReason): boolean | Promise; private onWillShutdown(): void { @@ -73,10 +73,10 @@ export abstract class WorkingCopyBackupTracker extends Disposable { // at the risk of corrupting a backup because the backup operation // might terminate at any given time now. As such, we need to disable // this tracker from performing more backups by cancelling pending - // operations and disposing our listeners. + // operations and suspending the tracker without resuming. this.cancelBackupOperations(); - this.dispose(); + this.suspendBackupOperations(); } diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index 2ea14315943bf..3b5299037eab9 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -48,7 +48,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp super(workingCopyBackupService, workingCopyService, logService, lifecycleService, filesConfigurationService, workingCopyEditorService, editorService, editorGroupService); } - protected async onBeforeShutdown(reason: ShutdownReason): Promise { + protected async onFinalBeforeShutdown(reason: ShutdownReason): Promise { // Important: we are about to shutdown and handle dirty working copies // and backups. We do not want any pending backup ops to interfer with @@ -343,18 +343,6 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp }, localize('revertBeforeShutdown', "Reverting editors with unsaved changes is taking longer than expected...")); } - private withProgressAndCancellation(promiseFactory: (token: CancellationToken) => Promise, title: string, detail?: string): Promise { - const cts = new CancellationTokenSource(); - - return this.progressService.withProgress({ - location: ProgressLocation.Dialog, // use a dialog to prevent the user from making any more changes now (https://github.com/microsoft/vscode/issues/122774) - cancellable: true, // allow to cancel (https://github.com/microsoft/vscode/issues/112278) - delay: 800, // delay notification so that it only appears when operation takes a long time - title, - detail - }, () => raceCancellation(promiseFactory(cts.token), cts.token), () => cts.dispose(true)); - } - private async noVeto(backupsToDiscard: IWorkingCopyIdentifier[]): Promise { // Discard backups from working copies the @@ -400,21 +388,36 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp return; } - // When we shutdown either with no dirty working copies left - // or with some handled, we start to discard these backups - // to free them up. This helps to get rid of stale backups - // as reported in https://github.com/microsoft/vscode/issues/92962 - // - // However, we never want to discard backups that we know - // were not restored in the session. - try { - if (Array.isArray(arg1)) { - await Promises.settled(arg1.map(workingCopy => this.workingCopyBackupService.discardBackup(workingCopy))); - } else { - await this.workingCopyBackupService.discardBackups(arg1); + await this.withProgressAndCancellation(async () => { + + // When we shutdown either with no dirty working copies left + // or with some handled, we start to discard these backups + // to free them up. This helps to get rid of stale backups + // as reported in https://github.com/microsoft/vscode/issues/92962 + // + // However, we never want to discard backups that we know + // were not restored in the session. + try { + if (Array.isArray(arg1)) { + await Promises.settled(arg1.map(workingCopy => this.workingCopyBackupService.discardBackup(workingCopy))); + } else { + await this.workingCopyBackupService.discardBackups(arg1); + } + } catch (error) { + this.logService.error(`[backup tracker] error discarding backups: ${error}`); } - } catch (error) { - this.logService.error(`[backup tracker] error discarding backups: ${error}`); - } + }, localize('discardBackupsBeforeShutdown', "Discarding backups is taking longer than expected...")); + } + + private withProgressAndCancellation(promiseFactory: (token: CancellationToken) => Promise, title: string, detail?: string): Promise { + const cts = new CancellationTokenSource(); + + return this.progressService.withProgress({ + location: ProgressLocation.Dialog, // use a dialog to prevent the user from making any more changes now (https://github.com/microsoft/vscode/issues/122774) + cancellable: true, // allow to cancel (https://github.com/microsoft/vscode/issues/112278) + delay: 800, // delay so that it only appears when operation takes a long time + title, + detail + }, () => raceCancellation(promiseFactory(cts.token), cts.token), () => cts.dispose(true)); } } From 0e8c30c2381db0e61671e2e418c71e78516f7724 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 09:32:56 +0100 Subject: [PATCH 0406/2210] :lipstick: --- src/vs/editor/contrib/comment/comment.ts | 6 ++++-- src/vs/editor/contrib/indentation/indentation.ts | 8 ++++++-- src/vs/editor/contrib/linkedEditing/linkedEditing.ts | 1 - src/vs/editor/contrib/wordOperations/wordOperations.ts | 3 ++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/comment/comment.ts b/src/vs/editor/contrib/comment/comment.ts index 97d3f443b46c3..7731a7bf6a2ff 100644 --- a/src/vs/editor/contrib/comment/comment.ts +++ b/src/vs/editor/contrib/comment/comment.ts @@ -27,6 +27,8 @@ abstract class CommentLineAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + const languageConfigurationService = accessor.get(ILanguageConfigurationService); + if (!editor.hasModel()) { return; } @@ -56,7 +58,6 @@ abstract class CommentLineAction extends EditorAction { } } - const languageConfigurationService = accessor.get(ILanguageConfigurationService); for (const selection of selections) { commands.push(new LineCommentCommand( @@ -155,6 +156,8 @@ class BlockCommentAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + const languageConfigurationService = accessor.get(ILanguageConfigurationService); + if (!editor.hasModel()) { return; } @@ -162,7 +165,6 @@ class BlockCommentAction extends EditorAction { const commentsOptions = editor.getOption(EditorOption.comments); const commands: ICommand[] = []; const selections = editor.getSelections(); - const languageConfigurationService = accessor.get(ILanguageConfigurationService); for (const selection of selections) { commands.push(new BlockCommentCommand(selection, commentsOptions.insertSpace, languageConfigurationService)); } diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index b0ecc91200e3a..aff4e9da15d12 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -310,11 +310,13 @@ export class ReindentLinesAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + const languageConfigurationService = accessor.get(ILanguageConfigurationService); + let model = editor.getModel(); if (!model) { return; } - let edits = getReindentEditOperations(model, accessor.get(ILanguageConfigurationService), 1, model.getLineCount()); + let edits = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount()); if (edits.length > 0) { editor.pushUndoStop(); editor.executeEdits(this.id, edits); @@ -334,6 +336,8 @@ export class ReindentSelectedLinesAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + const languageConfigurationService = accessor.get(ILanguageConfigurationService); + let model = editor.getModel(); if (!model) { return; @@ -362,7 +366,7 @@ export class ReindentSelectedLinesAction extends EditorAction { startLineNumber--; } - let editOperations = getReindentEditOperations(model, accessor.get(ILanguageConfigurationService), startLineNumber, endLineNumber); + let editOperations = getReindentEditOperations(model, languageConfigurationService, startLineNumber, endLineNumber); edits.push(...editOperations); } diff --git a/src/vs/editor/contrib/linkedEditing/linkedEditing.ts b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts index b3ada5326db02..f8af54813f953 100644 --- a/src/vs/editor/contrib/linkedEditing/linkedEditing.ts +++ b/src/vs/editor/contrib/linkedEditing/linkedEditing.ts @@ -456,4 +456,3 @@ registerModelAndPositionCommand('_executeLinkedEditingProvider', (model, positio registerEditorContribution(LinkedEditingContribution.ID, LinkedEditingContribution); registerEditorAction(LinkedEditingAction); - diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index d514b0efa19b1..665a69fbfba15 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -331,6 +331,8 @@ export abstract class DeleteWordCommand extends EditorCommand { } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + const languageConfigurationService = accessor.get(ILanguageConfigurationService); + if (!editor.hasModel()) { return; } @@ -339,7 +341,6 @@ export abstract class DeleteWordCommand extends EditorCommand { const selections = editor.getSelections(); const autoClosingBrackets = editor.getOption(EditorOption.autoClosingBrackets); const autoClosingQuotes = editor.getOption(EditorOption.autoClosingQuotes); - const languageConfigurationService = accessor.get(ILanguageConfigurationService); const autoClosingPairs = languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getAutoClosingPairs(); const viewModel = editor._getViewModel(); From 6685f8c1339397d75792a0775b37be5066d9a67f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 09:58:59 +0100 Subject: [PATCH 0407/2210] always use extension id from package.json for extension descriptions --- src/vs/workbench/services/extensions/common/extensions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 8f9b89d41b9aa..e6c417fe460a7 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription, IExtensionContributions } from 'vs/platform/extensions/common/extensions'; -import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ApiProposalName } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; @@ -344,7 +344,7 @@ export function toExtension(extensionDescription: IExtensionDescription): IExten export function toExtensionDescription(extension: IExtension, isUnderDevelopment?: boolean): IExtensionDescription { return { - identifier: new ExtensionIdentifier(extension.identifier.id), + identifier: new ExtensionIdentifier(getExtensionId(extension.manifest.publisher, extension.manifest.name)), isBuiltin: extension.type === ExtensionType.System, isUserBuiltin: extension.type === ExtensionType.User && extension.isBuiltin, isUnderDevelopment: !!isUnderDevelopment, From fbad065eea9df22077be23e428df4def8a5d4a82 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Dec 2021 10:09:30 +0100 Subject: [PATCH 0408/2210] Playwright: record a trace per failing test, not suite (#138600) * smoke - record traces per test and not entire suite * smoke - only persist failing tests * smoke - cleanup * smoke - more logging * smoke - push a test failure to proof the point * smoke - switch back to chrome for smoke tests * smoke - warn when exit takes 10s * Revert "smoke - push a test failure to proof the point" This reverts commit e572a0c40ddc2eede4102e0ca76434c6b09f9dcd. --- .../win32/product-build-win32.yml | 2 +- src/vs/platform/driver/common/driver.ts | 2 + .../platform/driver/electron-main/driver.ts | 8 ++++ src/vs/platform/driver/node/driver.ts | 10 +++++ test/automation/src/application.ts | 8 ++++ test/automation/src/code.ts | 15 ++++++- test/automation/src/playwrightDriver.ts | 40 ++++++++++++------- test/smoke/src/areas/editor/editor.test.ts | 7 ++-- .../src/areas/extensions/extensions.test.ts | 10 ++--- .../src/areas/languages/languages.test.ts | 7 ++-- .../src/areas/multiroot/multiroot.test.ts | 8 ++-- .../smoke/src/areas/notebook/notebook.test.ts | 8 ++-- .../src/areas/preferences/preferences.test.ts | 9 +++-- test/smoke/src/areas/search/search.test.ts | 13 +++--- .../src/areas/statusbar/statusbar.test.ts | 7 ++-- .../smoke/src/areas/terminal/terminal.test.ts | 6 +-- .../src/areas/workbench/data-loss.test.ts | 7 ++-- test/smoke/src/areas/workbench/launch.test.ts | 5 +-- .../src/areas/workbench/localization.test.ts | 4 +- test/smoke/src/utils.ts | 23 +++++++++-- 20 files changed, 135 insertions(+), 64 deletions(-) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index f3c5344595012..f37e749c99fd6 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -229,7 +229,7 @@ steps: . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --web --browser firefox --headless } + exec { yarn smoketest-no-compile --web --headless } displayName: Run smoke tests (Browser) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts index 41b7809f4485c..717e6c82307c3 100644 --- a/src/vs/platform/driver/common/driver.ts +++ b/src/vs/platform/driver/common/driver.ts @@ -41,6 +41,8 @@ export interface IDriver { getWindowIds(): Promise; capturePage(windowId: number): Promise; + startTracing(windowId: number, name: string): Promise; + stopTracing(windowId: number, name: string, persist: boolean): Promise; reloadWindow(windowId: number): Promise; exitApplication(): Promise; dispatchKeybinding(windowId: number, keybinding: string): Promise; diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 34f68536dad4f..17d85fa459225 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -69,6 +69,14 @@ export class Driver implements IDriver, IWindowDriverRegistry { return image.toPNG().toString('base64'); } + async startTracing(windowId: number, name: string): Promise { + // ignore - tracing is not implemented yet + } + + async stopTracing(windowId: number, name: string, persist: boolean): Promise { + // ignore - tracing is not implemented yet + } + async reloadWindow(windowId: number): Promise { await this.whenUnfrozen(windowId); diff --git a/src/vs/platform/driver/node/driver.ts b/src/vs/platform/driver/node/driver.ts index 6b30de6beeccd..9f05cb20932d4 100644 --- a/src/vs/platform/driver/node/driver.ts +++ b/src/vs/platform/driver/node/driver.ts @@ -21,6 +21,8 @@ export class DriverChannel implements IServerChannel { switch (command) { case 'getWindowIds': return this.driver.getWindowIds(); case 'capturePage': return this.driver.capturePage(arg); + case 'startTracing': return this.driver.startTracing(arg[0], arg[1]); + case 'stopTracing': return this.driver.stopTracing(arg[0], arg[1], arg[2]); case 'reloadWindow': return this.driver.reloadWindow(arg); case 'exitApplication': return this.driver.exitApplication(); case 'dispatchKeybinding': return this.driver.dispatchKeybinding(arg[0], arg[1]); @@ -56,6 +58,14 @@ export class DriverChannelClient implements IDriver { return this.channel.call('capturePage', windowId); } + startTracing(windowId: number, name: string): Promise { + return this.channel.call('startTracing', [windowId, name]); + } + + stopTracing(windowId: number, name: string, persist: boolean): Promise { + return this.channel.call('stopTracing', [windowId, name, persist]); + } + reloadWindow(windowId: number): Promise { return this.channel.call('reloadWindow', windowId); } diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index d6e024dd54857..436c2fd9073ab 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -104,6 +104,14 @@ export class Application { } } + async startTracing(name: string): Promise { + await this._code?.startTracing(name); + } + + async stopTracing(name: string, persist: boolean): Promise { + await this._code?.stopTracing(name, persist); + } + private async startApplication(extraArgs: string[] = []): Promise { this._code = await spawn({ ...this.options, diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index c59a03eda5fe3..acf35f9a823dc 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -27,7 +27,6 @@ export interface SpawnOptions { web?: boolean; headless?: boolean; browser?: 'chromium' | 'webkit' | 'firefox'; - suiteTitle?: string; testTitle?: string; } @@ -134,6 +133,16 @@ export class Code { return await this.driver.capturePage(windowId); } + async startTracing(name: string): Promise { + const windowId = await this.getActiveWindowId(); + return await this.driver.startTracing(windowId, name); + } + + async stopTracing(name: string, persist: boolean): Promise { + const windowId = await this.getActiveWindowId(); + return await this.driver.stopTracing(windowId, name, persist); + } + async waitForWindowIds(fn: (windowIds: number[]) => boolean): Promise { await poll(() => this.driver.getWindowIds(), fn, `get window ids`); } @@ -161,6 +170,10 @@ export class Code { while (!done) { retries++; + if (retries > 20) { + console.warn('Smoke test exit call did not terminate process after 10s, still trying...'); + } + if (retries > 40) { done = true; reject(new Error('Smoke test exit call did not terminate process after 20s, giving up')); diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index ac8807a963b7c..b1a5db3b508ae 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -43,8 +43,7 @@ class PlaywrightDriver implements IDriver { private readonly server: ChildProcess, private readonly browser: playwright.Browser, private readonly context: playwright.BrowserContext, - private readonly page: playwright.Page, - private readonly suiteTitle: string | undefined + private readonly page: playwright.Page ) { } @@ -56,20 +55,34 @@ class PlaywrightDriver implements IDriver { return ''; } + async startTracing(windowId: number, name: string): Promise { + try { + await this.warnAfter(this.context.tracing.startChunk({ title: name }), 5000, 'Starting playwright trace took more than 5 seconds'); + } catch (error) { + console.warn(`Failed to start playwright tracing (chunk): ${error}`); + } + } + + async stopTracing(windowId: number, name: string, persist: boolean): Promise { + try { + let persistPath: string | undefined = undefined; + if (persist) { + persistPath = join(logsPath, `playwright-trace-${traceCounter++}-${name.replace(/\s+/g, '-')}.zip`); + } + + await this.warnAfter(this.context.tracing.stopChunk({ path: persistPath }), 5000, 'Stopping playwright trace took more than 5 seconds'); + } catch (error) { + console.warn(`Failed to stop playwright tracing (chunk): ${error}`); + } + } + async reloadWindow(windowId: number) { throw new Error('Unsupported'); } async exitApplication() { try { - let traceFileName: string; - if (this.suiteTitle) { - traceFileName = `playwright-trace-${traceCounter++}-${this.suiteTitle.replace(/\s+/g, '-')}.zip`; - } else { - traceFileName = `playwright-trace-${traceCounter++}.zip`; - } - - await this.warnAfter(this.context.tracing.stop({ path: join(logsPath, traceFileName) }), 5000, 'Stopping playwright trace took >5seconds'); + await this.warnAfter(this.context.tracing.stop(), 5000, 'Stopping playwright trace took >5seconds'); } catch (error) { console.warn(`Failed to stop playwright tracing: ${error}`); } @@ -196,7 +209,6 @@ let port = 9000; export interface PlaywrightOptions { readonly browser?: 'chromium' | 'webkit' | 'firefox'; readonly headless?: boolean; - readonly suiteTitle?: string; } export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, options: PlaywrightOptions = {}): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver }> { @@ -212,7 +224,7 @@ export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_P client: { dispose: () => { /* there is no client to dispose for browser, teardown is triggered via exitApplication call */ } }, - driver: new PlaywrightDriver(serverProcess, browser, context, page, options.suiteTitle) + driver: new PlaywrightDriver(serverProcess, browser, context, page) }; } @@ -275,9 +287,9 @@ async function launchBrowser(options: PlaywrightOptions, endpoint: string, works const context = await browser.newContext(); try { - await context.tracing.start({ screenshots: true, snapshots: true, sources: true, title: options.suiteTitle }); + await context.tracing.start({ screenshots: true, snapshots: true, sources: true }); } catch (error) { - console.warn(`Failed to start playwright tracing.`); // do not fail the build when this fails + console.warn(`Failed to start playwright tracing: ${error}`); // do not fail the build when this fails } const page = await context.newPage(); diff --git a/test/smoke/src/areas/editor/editor.test.ts b/test/smoke/src/areas/editor/editor.test.ts index f64f3f658f9d5..c57672a04caf0 100644 --- a/test/smoke/src/areas/editor/editor.test.ts +++ b/test/smoke/src/areas/editor/editor.test.ts @@ -5,12 +5,13 @@ import minimist = require('minimist'); import { Application } from '../../../../automation'; -import { afterSuite, beforeSuite } from '../../utils'; +import { installCommonTestHandlers } from '../../utils'; export function setup(opts: minimist.ParsedArgs) { describe('Editor', () => { - beforeSuite(opts); - afterSuite(opts); + + // Shared before/after handling + installCommonTestHandlers(opts); it('shows correct quick outline', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index 5ee13542b00a0..47d327ce993f1 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -5,14 +5,15 @@ import minimist = require('minimist'); import { Application, Quality } from '../../../../automation'; -import { afterSuite, beforeSuite } from '../../utils'; +import { installCommonTestHandlers } from '../../utils'; export function setup(opts: minimist.ParsedArgs) { describe('Extensions', () => { - beforeSuite(opts); - afterSuite(opts); - it(`install and enable vscode-smoketest-check extension`, async function () { + // Shared before/after handling + installCommonTestHandlers(opts); + + it('install and enable vscode-smoketest-check extension', async function () { const app = this.app as Application; if (app.quality === Quality.Dev) { @@ -29,6 +30,5 @@ export function setup(opts: minimist.ParsedArgs) { await app.workbench.quickaccess.runCommand('Smoke Test Check'); }); - }); } diff --git a/test/smoke/src/areas/languages/languages.test.ts b/test/smoke/src/areas/languages/languages.test.ts index b4d6f67f130cf..b17e8c776f443 100644 --- a/test/smoke/src/areas/languages/languages.test.ts +++ b/test/smoke/src/areas/languages/languages.test.ts @@ -5,12 +5,13 @@ import minimist = require('minimist'); import { Application, ProblemSeverity, Problems } from '../../../../automation/out'; -import { afterSuite, beforeSuite } from '../../utils'; +import { installCommonTestHandlers } from '../../utils'; export function setup(opts: minimist.ParsedArgs) { describe('Language Features', () => { - beforeSuite(opts); - afterSuite(opts); + + // Shared before/after handling + installCommonTestHandlers(opts); it('verifies quick outline', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index 62f3026d5179e..06cd52eb2d6aa 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import minimist = require('minimist'); import * as path from 'path'; import { Application } from '../../../../automation'; -import { afterSuite, beforeSuite } from '../../utils'; +import { installCommonTestHandlers } from '../../utils'; function toUri(path: string): string { if (process.platform === 'win32') { @@ -38,13 +38,13 @@ function createWorkspaceFile(workspacePath: string): string { export function setup(opts: minimist.ParsedArgs) { describe('Multiroot', () => { - beforeSuite(opts, async opts => { + + // Shared before/after handling + installCommonTestHandlers(opts, async opts => { const workspacePath = createWorkspaceFile(opts.workspacePath); return { ...opts, workspacePath }; }); - afterSuite(opts); - it('shows results from all folders', async function () { const app = this.app as Application; await app.workbench.quickaccess.openQuickAccess('*.*'); diff --git a/test/smoke/src/areas/notebook/notebook.test.ts b/test/smoke/src/areas/notebook/notebook.test.ts index 55a471578b42c..40941d8603b85 100644 --- a/test/smoke/src/areas/notebook/notebook.test.ts +++ b/test/smoke/src/areas/notebook/notebook.test.ts @@ -6,11 +6,13 @@ import * as cp from 'child_process'; import minimist = require('minimist'); import { Application } from '../../../../automation'; -import { afterSuite, beforeSuite } from '../../utils'; +import { installCommonTestHandlers } from '../../utils'; export function setup(opts: minimist.ParsedArgs) { describe.skip('Notebooks', () => { - beforeSuite(opts); + + // Shared before/after handling + installCommonTestHandlers(opts); afterEach(async function () { const app = this.app as Application; @@ -24,8 +26,6 @@ export function setup(opts: minimist.ParsedArgs) { cp.execSync('git reset --hard HEAD --quiet', { cwd: app.workspacePathOrFolder }); }); - afterSuite(opts); - it.skip('inserts/edits code cell', async function () { const app = this.app as Application; await app.workbench.notebook.openNotebook(); diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index af363b0866170..6f2aee54287e1 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -5,12 +5,13 @@ import minimist = require('minimist'); import { Application, ActivityBarPosition } from '../../../../automation'; -import { afterSuite, beforeSuite } from '../../utils'; +import { installCommonTestHandlers } from '../../utils'; export function setup(opts: minimist.ParsedArgs) { describe('Preferences', () => { - beforeSuite(opts); - afterSuite(opts); + + // Shared before/after handling + installCommonTestHandlers(opts); it('turns off editor line numbers and verifies the live change', async function () { const app = this.app as Application; @@ -23,7 +24,7 @@ export function setup(opts: minimist.ParsedArgs) { await app.code.waitForElements('.line-numbers', false, result => !result || result.length === 0); }); - it(`changes 'workbench.action.toggleSidebarPosition' command key binding and verifies it`, async function () { + it('changes "workbench.action.toggleSidebarPosition" command key binding and verifies it', async function () { const app = this.app as Application; await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.LEFT); diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 121a7aea927ed..4e3d674110461 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -6,11 +6,13 @@ import * as cp from 'child_process'; import minimist = require('minimist'); import { Application } from '../../../../automation'; -import { afterSuite, beforeSuite, retry } from '../../utils'; +import { installCommonTestHandlers, retry } from '../../utils'; export function setup(opts: minimist.ParsedArgs) { describe('Search', () => { - beforeSuite(opts); + + // Shared before/after handling + installCommonTestHandlers(opts); after(function () { const app = this.app as Application; @@ -18,8 +20,6 @@ export function setup(opts: minimist.ParsedArgs) { retry(async () => cp.execSync('git reset --hard HEAD --quiet', { cwd: app.workspacePathOrFolder }), 0, 5); }); - afterSuite(opts); - // https://github.com/microsoft/vscode/issues/124146 it.skip /* https://github.com/microsoft/vscode/issues/124335 */('has a tooltp with a keybinding', async function () { const app = this.app as Application; @@ -73,8 +73,9 @@ export function setup(opts: minimist.ParsedArgs) { }); describe('Quick Access', () => { - beforeSuite(opts); - afterSuite(opts); + + // Shared before/after handling + installCommonTestHandlers(opts); it('quick access search produces correct result', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index b59d8c3ce5013..89e5b6e63568c 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -5,12 +5,13 @@ import minimist = require('minimist'); import { Application, Quality, StatusBarElement } from '../../../../automation'; -import { afterSuite, beforeSuite } from '../../utils'; +import { installCommonTestHandlers } from '../../utils'; export function setup(opts: minimist.ParsedArgs) { describe('Statusbar', () => { - beforeSuite(opts); - afterSuite(opts); + + // Shared before/after handling + installCommonTestHandlers(opts); it('verifies presence of all default status bar elements', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/terminal/terminal.test.ts b/test/smoke/src/areas/terminal/terminal.test.ts index 64c078a1e45a6..ce9e3a0d705db 100644 --- a/test/smoke/src/areas/terminal/terminal.test.ts +++ b/test/smoke/src/areas/terminal/terminal.test.ts @@ -5,7 +5,7 @@ import minimist = require('minimist'); import { Application, Terminal, TerminalCommandId } from '../../../../automation/out'; -import { afterSuite, beforeSuite } from '../../utils'; +import { installCommonTestHandlers } from '../../utils'; import { setup as setupTerminalEditorsTests } from './terminal-editors.test'; import { setup as setupTerminalPersistenceTests } from './terminal-persistence.test'; import { setup as setupTerminalProfileTests } from './terminal-profiles.test'; @@ -21,8 +21,8 @@ export function setup(opts: minimist.ParsedArgs) { // Retry tests 3 times to minimize build failures due to any flakiness this.retries(3); - beforeSuite(opts); - afterSuite(opts); + // Shared before/after handling + installCommonTestHandlers(opts); let terminal: Terminal; before(async function () { diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 6ea0d9a5b3730..24be2f5743ef8 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -5,15 +5,14 @@ import { Application, ApplicationOptions, Quality } from '../../../../automation/out'; import { ParsedArgs } from 'minimist'; -import { afterSuite, getRandomUserDataDir, startApp, timeout } from '../../utils'; +import { installCommonAfterHandlers, getRandomUserDataDir, startApp, timeout } from '../../utils'; export function setup(opts: ParsedArgs) { - describe('Data Loss (insiders -> insiders)', () => { let app: Application | undefined = undefined; - afterSuite(opts, () => app); + installCommonAfterHandlers(opts, () => app); it('verifies opened editors are restored', async function () { app = await startApp(opts, this.defaultOptions); @@ -98,7 +97,7 @@ export function setup(opts: ParsedArgs) { let insidersApp: Application | undefined = undefined; let stableApp: Application | undefined = undefined; - afterSuite(opts, () => insidersApp ?? stableApp, async () => stableApp?.stop()); + installCommonAfterHandlers(opts, () => insidersApp ?? stableApp, async () => stableApp?.stop()); it('verifies opened editors are restored', async function () { const stableCodePath = opts['stable-build']; diff --git a/test/smoke/src/areas/workbench/launch.test.ts b/test/smoke/src/areas/workbench/launch.test.ts index f46a3bdc994b9..dc17c9dc14c0a 100644 --- a/test/smoke/src/areas/workbench/launch.test.ts +++ b/test/smoke/src/areas/workbench/launch.test.ts @@ -6,15 +6,14 @@ import minimist = require('minimist'); import { join } from 'path'; import { Application } from '../../../../automation'; -import { afterSuite, startApp } from '../../utils'; +import { installCommonAfterHandlers, startApp } from '../../utils'; export function setup(args: minimist.ParsedArgs) { - describe('Launch', () => { let app: Application | undefined; - afterSuite(args, () => app); + installCommonAfterHandlers(args, () => app); it(`verifies that application launches when user data directory has non-ascii characters`, async function () { const massagedOptions = { ...this.defaultOptions, userDataDir: join(this.defaultOptions.userDataDir, 'ø') }; diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index 9e27c5ba405fa..27ce308a90230 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -5,7 +5,7 @@ import minimist = require('minimist'); import { Application, Quality } from '../../../../automation'; -import { afterSuite, startApp } from '../../utils'; +import { installCommonAfterHandlers, startApp } from '../../utils'; export function setup(args: minimist.ParsedArgs) { @@ -13,7 +13,7 @@ export function setup(args: minimist.ParsedArgs) { let app: Application | undefined = undefined; - afterSuite(args, () => app); + installCommonAfterHandlers(args, () => app); it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () { if (this.defaultOptions.quality === Quality.Dev || this.defaultOptions.remote) { diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index fa960486b96ed..134c9d236c097 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -19,13 +19,16 @@ export function itRepeat(n: number, description: string, callback: (this: Contex } } -export function beforeSuite(args: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { +export function installCommonTestHandlers(args: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { + installCommonBeforeHandlers(args, optionsTransform); + installCommonAfterHandlers(args); +} + +export function installCommonBeforeHandlers(args: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { before(async function () { const testTitle = this.currentTest?.title; - const suiteTitle = this.currentTest?.parent?.title; this.app = await startApp(args, this.defaultOptions, async opts => { - opts.suiteTitle = suiteTitle; opts.testTitle = testTitle; if (optionsTransform) { @@ -35,6 +38,12 @@ export function beforeSuite(args: minimist.ParsedArgs, optionsTransform?: (opts: return opts; }); }); + + beforeEach(async function () { + if (this.app) { + await this.app.startTracing(this.currentTest?.title); + } + }); } export async function startApp(args: minimist.ParsedArgs, options: ApplicationOptions, optionsTransform?: (opts: ApplicationOptions) => Promise): Promise { @@ -66,7 +75,7 @@ export function getRandomUserDataDir(options: ApplicationOptions): string { return options.userDataDir.concat(`-${userDataPathSuffix}`); } -export function afterSuite(opts: minimist.ParsedArgs, appFn?: () => Application | undefined, joinFn?: () => Promise) { +export function installCommonAfterHandlers(opts: minimist.ParsedArgs, appFn?: () => Application | undefined, joinFn?: () => Promise) { after(async function () { const app: Application = appFn?.() ?? this.app; @@ -87,6 +96,12 @@ export function afterSuite(opts: minimist.ParsedArgs, appFn?: () => Application await joinFn(); } }); + + afterEach(async function () { + if (this.app) { + await this.app.stopTracing(this.currentTest?.title, this.currentTest?.state === 'failed'); + } + }); } export function timeout(i: number) { From 52eaad07614756b68e86aad1efdb6fb7cae33dfe Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Dec 2021 10:17:19 +0100 Subject: [PATCH 0409/2210] ci - add name of browser to web tests --- .github/workflows/ci.yml | 12 ++++++------ .../azure-pipelines/darwin/product-build-darwin.yml | 6 +++--- build/azure-pipelines/linux/product-build-linux.yml | 6 +++--- build/azure-pipelines/win32/product-build-win32.yml | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4a8bd601dcd7..c8043636acd93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: - name: Run Unit Tests (node.js) run: yarn test-node - - name: Run Unit Tests (Browser) + - name: Run Unit Tests (Browser, Chromium) run: yarn test-browser --browser chromium - name: Compile Integration Tests @@ -84,7 +84,7 @@ jobs: - name: Run Integration Tests (Electron) run: .\scripts\test-integration.bat - - name: Run Integration Tests (Browser) + - name: Run Integration Tests (Browser, Firefox) timeout-minutes: 10 run: .\resources\server\test\test-web-integration.bat --browser firefox @@ -154,7 +154,7 @@ jobs: id: nodejs-unit-tests run: yarn test-node - - name: Run Unit Tests (Browser) + - name: Run Unit Tests (Browser, Chromium) id: browser-unit-tests run: DISPLAY=:10 yarn test-browser --browser chromium @@ -165,7 +165,7 @@ jobs: id: electron-integration-tests run: DISPLAY=:10 ./scripts/test-integration.sh - - name: Run Integration Tests (Browser) + - name: Run Integration Tests (Browser, Chromium) id: browser-integration-tests run: DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium @@ -232,7 +232,7 @@ jobs: - name: Run Unit Tests (node.js) run: yarn test-node - - name: Run Unit Tests (Browser) + - name: Run Unit Tests (Browser, Chromium) run: DISPLAY=:10 yarn test-browser --browser chromium - name: Compile Integration Tests @@ -241,7 +241,7 @@ jobs: - name: Run Integration Tests (Electron) run: DISPLAY=:10 ./scripts/test-integration.sh - - name: Run Integration Tests (Browser) + - name: Run Integration Tests (Browser, Webkit) run: DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser webkit - name: Run Integration Tests (Remote) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 39e73b2a66172..5ca9eeea492ee 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -182,7 +182,7 @@ steps: - script: | set -e yarn test-browser --build --browser chromium --browser webkit --browser firefox --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser) + displayName: Run unit tests (Browser, Chromium & Firefox & Webkit) timeoutInMinutes: 7 condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -204,7 +204,7 @@ steps: set -e VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ ./resources/server/test/test-web-integration.sh --browser webkit - displayName: Run integration tests (Browser) + displayName: Run integration tests (Browser, Webkit) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -243,7 +243,7 @@ steps: VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ yarn smoketest-no-compile --web --headless timeoutInMinutes: 10 - displayName: Run smoke tests (Browser) + displayName: Run smoke tests (Browser, Chromium) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - task: PublishPipelineArtifact@0 diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 23c03767a16c1..2a6128133097a 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -172,7 +172,7 @@ steps: - script: | set -e yarn test-browser --build --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser) + displayName: Run unit tests (Browser, Chromium) timeoutInMinutes: 7 condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -195,7 +195,7 @@ steps: set -e VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ ./resources/server/test/test-web-integration.sh --browser chromium - displayName: Run integration tests (Browser) + displayName: Run integration tests (Browser, Chromium) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -233,7 +233,7 @@ steps: VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ yarn smoketest-no-compile --web --headless --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" timeoutInMinutes: 10 - displayName: Run smoke tests (Browser) + displayName: Run smoke tests (Browser, Chromium) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - task: PublishPipelineArtifact@0 diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index f37e749c99fd6..c9b28addafd2d 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -162,7 +162,7 @@ steps: . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" exec { yarn test-browser --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } - displayName: Run unit tests (Browser) + displayName: Run unit tests (Browser, Chromium & Firefox) timeoutInMinutes: 7 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) @@ -184,7 +184,7 @@ steps: . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-web-integration.bat --browser firefox } - displayName: Run integration tests (Browser) + displayName: Run integration tests (Browser, Firefox) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) @@ -230,7 +230,7 @@ steps: $ErrorActionPreference = "Stop" $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" exec { yarn smoketest-no-compile --web --headless } - displayName: Run smoke tests (Browser) + displayName: Run smoke tests (Browser, Chromium) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) From 2c68d4de7eba796f303578c5839482ae0db84384 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Dec 2021 10:50:27 +0100 Subject: [PATCH 0410/2210] smoke - always log with trace --- test/smoke/src/main.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 9c2027eafb7d4..b66acfa5aae9b 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -296,20 +296,25 @@ async function setup(): Promise { console.log('*** Smoketest setup done!\n'); } -function createOptions(): ApplicationOptions { +async function createOptions(): Promise { const loggers: Logger[] = []; if (opts.verbose) { loggers.push(new ConsoleLogger()); } - let log: string | undefined = undefined; - + const log: string | undefined = 'trace'; // because smoke tests are flaky + let logsPath: string; if (opts.log) { - loggers.push(new FileLogger(opts.log)); - log = 'trace'; + logsPath = opts.log; + } else { + logsPath = path.join(repoPath, '.build', 'logs', opts.web ? 'smoke-tests-browser' : opts.remote ? 'smoke-tests-remote' : 'smoke-tests', 'smoke-test-runner.log'); } + await mkdirp(path.dirname(logsPath)); + + loggers.push(new FileLogger(logsPath)); + return { quality, codePath: opts.build, @@ -332,7 +337,7 @@ function createOptions(): ApplicationOptions { before(async function () { this.timeout(2 * 60 * 1000); // allow two minutes for setup await setup(); - this.defaultOptions = createOptions(); + this.defaultOptions = await createOptions(); }); after(async function () { From fbc01b955e1a970fdb3303ea717ee963243acd1b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 11:02:13 +0100 Subject: [PATCH 0411/2210] Fix #138591 --- .../extensionManagement/common/extensionGalleryService.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index f547bdb49cf03..160039e784fef 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -313,7 +313,10 @@ function getIconAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAs function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset | null { const result = version.files.filter(f => f.assetType === type)[0]; - return result ? { uri: `${version.assetUri}/${type}`, fallbackUri: `${version.fallbackAssetUri}/${type}` } : null; + return result ? { + uri: `${version.assetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`, + fallbackUri: `${version.fallbackAssetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}` + } : null; } function getExtensions(version: IRawGalleryExtensionVersion, property: string): string[] { @@ -1035,7 +1038,7 @@ export async function resolveMarketplaceHeaders(version: string, productService: } | undefined): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, - 'User-Agent': `VSCode ${version}` + 'User-Agent': `VSCode ${version} (${productService.nameShort})` }; const uuid = await getServiceMachineId(environmentService, fileService, storageService); if (supportsTelemetry(productService, environmentService) && getTelemetryLevel(configurationService) === TelemetryLevel.USAGE) { From 322a4c631bc70c4ede37b02d9d129ddbf15c10aa Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 11:41:00 +0100 Subject: [PATCH 0412/2210] Fix #138490 --- .../electron-browser/extensionRecommendationsService.test.ts | 1 + .../extensions/test/electron-browser/extensionsActions.test.ts | 1 + .../extensions/test/electron-browser/extensionsViews.test.ts | 1 + .../test/electron-browser/extensionsWorkbenchService.test.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index 7f7e90d512ee5..d3e72271f335a 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -267,6 +267,7 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stub(IExtensionGalleryService, 'isEnabled', true); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...mockExtensionGallery)); + instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []); prompted = false; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index fc1abff25b78d..54b70362743ef 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -136,6 +136,7 @@ async function setupTest() { instantiationService.stub(IURLService, NativeURLService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); + instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []); instantiationService.stub(IExtensionService, >{ getExtensions: () => Promise.resolve([]), onDidChangeExtensions: new Emitter().event, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false }); (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 3b08938208afb..d0714301adc17 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -165,6 +165,7 @@ suite('ExtensionsListView Tests', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localEnabledTheme, localEnabledLanguage, localRandom, localDisabledTheme, localDisabledLanguage, builtInTheme, builtInBasic]); instantiationService.stubPromise(IExtensionManagementService, 'getExtensgetExtensionsControlManifestionsReport', {}); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); + instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []); instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []); instantiationService.stub(IViewDescriptorService, { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index e950ee617d31e..85d2158c832f1 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -124,6 +124,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { setup(async () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); + instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []); instantiationService.stubPromise(INotificationService, 'prompt', 0); (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); }); From 258ca4f8a26e748b304c2474644677e84f3a886e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 11:47:23 +0100 Subject: [PATCH 0413/2210] Fix #138492 --- .../platform/windows/electron-main/window.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 6c888d7d89c28..14c5c498e1f25 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -137,7 +137,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { private readonly touchBarGroups: TouchBarSegmentedControl[] = []; - private marketplaceHeadersPromise: Promise; private currentHttpProxy: string | undefined = undefined; private currentNoProxy: string | undefined = undefined; @@ -150,7 +149,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { @ILogService private readonly logService: ILogService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IFileService private readonly fileService: IFileService, - @IStorageMainService storageMainService: IStorageMainService, + @IStorageMainService private readonly storageMainService: IStorageMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeMainService private readonly themeMainService: IThemeMainService, @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, @@ -303,12 +302,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { // macOS: touch bar support this.createTouchBar(); - // Request handling - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.productService.version, this.productService, this.environmentMainService, this.configurationService, this.fileService, { - get: key => storageMainService.globalStorage.get(key), - store: (key, value) => storageMainService.globalStorage.set(key, value) - }); - // Eventing this.registerListeners(); } @@ -542,12 +535,23 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, async (details, cb) => { - const headers = await this.marketplaceHeadersPromise; + const headers = await this.getMarketplaceHeaders(); cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) }); }); } + private marketplaceHeadersPromise: Promise | undefined; + private getMarketplaceHeaders(): Promise { + if (!this.marketplaceHeadersPromise) { + this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.productService.version, this.productService, this.environmentMainService, this.configurationService, this.fileService, { + get: key => this.storageMainService.globalStorage.get(key), + store: (key, value) => this.storageMainService.globalStorage.set(key, value) + }); + } + return this.marketplaceHeadersPromise; + } + private async onWindowError(error: WindowError.UNRESPONSIVE): Promise; private async onWindowError(error: WindowError.CRASHED, details: { reason: string, exitCode: number }): Promise; private async onWindowError(error: WindowError.LOAD, details: { reason: string, exitCode: number }): Promise; From e3544ad1597086493374250df2c25a88778abe56 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 12:41:52 +0100 Subject: [PATCH 0414/2210] Moves out Comparator logic from inline completions to arrays.ts. --- src/vs/base/common/arrays.ts | 40 +++++++++++++++++++ src/vs/base/test/common/arrays.test.ts | 18 +++++++++ .../suggestWidgetInlineCompletionProvider.ts | 4 +- .../editor/contrib/inlineCompletions/utils.ts | 20 ---------- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 892269c078a2e..b45602d37e158 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -625,6 +625,46 @@ export function maxIndex(array: readonly T[], fn: (value: T) => number): numb return maxIdx; } +/** + * A comparator `c` defines a total order `<=` on `T` as following: + * `c(a, b) <= 0` iff `a` <= `b`. + * We also have `c(a, b) == 0` iff `c(b, a) == 0`. +*/ +export type Comparator = (a: T, b: T) => number; + +export function compareBy(selector: (item: TItem) => TCompareBy, comparator: Comparator): Comparator { + return (a, b) => comparator(selector(a), selector(b)); +} + +/** + * The natural order on numbers. +*/ +export const numberComparator: Comparator = (a, b) => a - b; + +/** + * Returns the first item that is equal to or greater than every other item. +*/ +export function findMaxBy(items: readonly T[], comparator: Comparator): T | undefined { + if (items.length === 0) { + return undefined; + } + + let min = items[0]; + for (const item of items) { + if (comparator(item, min) > 0) { + min = item; + } + } + return min; +} + +/** + * Returns the first item that is equal to or less than every other item. +*/ +export function findMinBy(items: readonly T[], comparator: Comparator): T | undefined { + return findMaxBy(items, (a, b) => -comparator(a, b)); +} + export class ArrayQueue { private firstIdx = 0; private lastIdx = this.items.length - 1; diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index 09e2ced01ca0d..ba1363a021b0b 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -354,6 +354,24 @@ suite('Arrays', () => { assert.strictEqual(arrays.maxIndex(array, value => value === 'b' ? 5 : 0), 1); }); + test('findMaxBy', () => { + const array = [{ v: 3 }, { v: 5 }, { v: 2 }, { v: 2 }, { v: 2 }, { v: 5 }]; + + assert.strictEqual( + array.indexOf(arrays.findMaxBy(array, arrays.compareBy(v => v.v, arrays.numberComparator))!), + 1 + ); + }); + + test('findMinBy', () => { + const array = [{ v: 3 }, { v: 5 }, { v: 2 }, { v: 2 }, { v: 2 }, { v: 5 }]; + + assert.strictEqual( + array.indexOf(arrays.findMinBy(array, arrays.compareBy(v => v.v, arrays.numberComparator))!), + 2 + ); + }); + suite('ArrayQueue', () => { suite('takeWhile/takeFromEndWhile', () => { test('TakeWhile 1', () => { diff --git a/src/vs/editor/contrib/inlineCompletions/suggestWidgetInlineCompletionProvider.ts b/src/vs/editor/contrib/inlineCompletions/suggestWidgetInlineCompletionProvider.ts index 54db56c27aa8d..b1aa08d6bf840 100644 --- a/src/vs/editor/contrib/inlineCompletions/suggestWidgetInlineCompletionProvider.ts +++ b/src/vs/editor/contrib/inlineCompletions/suggestWidgetInlineCompletionProvider.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { compareBy, findMaxBy, numberComparator } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -16,7 +17,6 @@ import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { minimizeInlineCompletion } from './inlineCompletionsModel'; import { NormalizedInlineCompletion, normalizedInlineCompletionsEquals } from './inlineCompletionToGhostText'; -import { compareBy, compareByNumber, findMaxBy } from './utils'; export interface SuggestWidgetState { /** @@ -108,7 +108,7 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable { const result = findMaxBy( candidates, - compareBy(s => s!.prefixLength, compareByNumber()) + compareBy(s => s!.prefixLength, numberComparator) ); return result ? result.index : - 1; } diff --git a/src/vs/editor/contrib/inlineCompletions/utils.ts b/src/vs/editor/contrib/inlineCompletions/utils.ts index 52b24d13f41ae..977669b7e809c 100644 --- a/src/vs/editor/contrib/inlineCompletions/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/utils.ts @@ -11,23 +11,3 @@ export function createDisposableRef(object: T, disposable?: IDisposable): IRe dispose: () => disposable?.dispose(), }; } - -export type Comparator = (a: T, b: T) => number; - -export function compareBy(selector: (item: TItem) => TCompareBy, comparator: Comparator): Comparator { - return (a, b) => comparator(selector(a), selector(b)); -} - -export function compareByNumber(): Comparator { - return (a, b) => a - b; -} - -export function findMaxBy(items: T[], comparator: Comparator): T | undefined { - let min: T | undefined = undefined; - for (const item of items) { - if (min === undefined || comparator(item, min) > 0) { - min = item; - } - } - return min; -} From 0d50a57bc585eecb65567b754a52a20455e81097 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 12:43:06 +0100 Subject: [PATCH 0415/2210] Removes unused minIndex/maxIndex. --- src/vs/base/common/arrays.ts | 34 -------------------------- src/vs/base/test/common/arrays.test.ts | 16 ------------ 2 files changed, 50 deletions(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index b45602d37e158..3dec71c692359 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -591,40 +591,6 @@ function getActualStartIndex(array: T[], start: number): number { return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length); } -/** - * Like Math.min with a delegate, and returns the winning index - */ -export function minIndex(array: readonly T[], fn: (value: T) => number): number { - let minValue = Number.MAX_SAFE_INTEGER; - let minIdx = 0; - array.forEach((value, i) => { - const thisValue = fn(value); - if (thisValue < minValue) { - minValue = thisValue; - minIdx = i; - } - }); - - return minIdx; -} - -/** - * Like Math.max with a delegate, and returns the winning index - */ -export function maxIndex(array: readonly T[], fn: (value: T) => number): number { - let minValue = Number.MIN_SAFE_INTEGER; - let maxIdx = 0; - array.forEach((value, i) => { - const thisValue = fn(value); - if (thisValue > minValue) { - minValue = thisValue; - maxIdx = i; - } - }); - - return maxIdx; -} - /** * A comparator `c` defines a total order `<=` on `T` as following: * `c(a, b) <= 0` iff `a` <= `b`. diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index ba1363a021b0b..cb44d799581ea 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -338,22 +338,6 @@ suite('Arrays', () => { assert.strictEqual(array[6], 7); }); - test('minIndex', () => { - const array = ['a', 'b', 'c']; - assert.strictEqual(arrays.minIndex(array, value => array.indexOf(value)), 0); - assert.strictEqual(arrays.minIndex(array, value => -array.indexOf(value)), 2); - assert.strictEqual(arrays.minIndex(array, _value => 0), 0); - assert.strictEqual(arrays.minIndex(array, value => value === 'b' ? 0 : 5), 1); - }); - - test('maxIndex', () => { - const array = ['a', 'b', 'c']; - assert.strictEqual(arrays.maxIndex(array, value => array.indexOf(value)), 2); - assert.strictEqual(arrays.maxIndex(array, value => -array.indexOf(value)), 0); - assert.strictEqual(arrays.maxIndex(array, _value => 0), 0); - assert.strictEqual(arrays.maxIndex(array, value => value === 'b' ? 5 : 0), 1); - }); - test('findMaxBy', () => { const array = [{ v: 3 }, { v: 5 }, { v: 2 }, { v: 2 }, { v: 2 }, { v: 5 }]; From aac8b0ce663b193d5c82245ae688c93c9eb37436 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 12:44:25 +0100 Subject: [PATCH 0416/2210] Improves findMaxBy. --- src/vs/base/common/arrays.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 3dec71c692359..8a275a99198aa 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -616,7 +616,8 @@ export function findMaxBy(items: readonly T[], comparator: Comparator): T } let min = items[0]; - for (const item of items) { + for (let i = 1; i < items.length; i++) { + const item = items[i]; if (comparator(item, min) > 0) { min = item; } From 10760909666ba6311968a9807f995ed278f6ce2d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 12:45:07 +0100 Subject: [PATCH 0417/2210] Improves variable naming in findMaxBy. --- src/vs/base/common/arrays.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 8a275a99198aa..c387b57d1dd39 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -615,14 +615,14 @@ export function findMaxBy(items: readonly T[], comparator: Comparator): T return undefined; } - let min = items[0]; + let max = items[0]; for (let i = 1; i < items.length; i++) { const item = items[i]; - if (comparator(item, min) > 0) { - min = item; + if (comparator(item, max) > 0) { + max = item; } } - return min; + return max; } /** From 983a71fad91653ea25f715084776d7a19dd8d65b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 12:45:56 +0100 Subject: [PATCH 0418/2210] Uses a single array to describe all cursors in CursorCollection to simplify code. --- .../common/controller/cursorCollection.ts | 133 ++++++------------ 1 file changed, 45 insertions(+), 88 deletions(-) diff --git a/src/vs/editor/common/controller/cursorCollection.ts b/src/vs/editor/common/controller/cursorCollection.ts index 9cc5b6dd40fe8..2a8f659c2e33b 100644 --- a/src/vs/editor/common/controller/cursorCollection.ts +++ b/src/vs/editor/common/controller/cursorCollection.ts @@ -3,44 +3,48 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { compareBy, findMaxBy, findMinBy } from 'vs/base/common/arrays'; import { CursorContext, CursorState, PartialCursorState } from 'vs/editor/common/controller/cursorCommon'; import { Cursor } from 'vs/editor/common/controller/oneCursor'; import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; export class CursorCollection { private context: CursorContext; - private primaryCursor: Cursor; - private secondaryCursors: Cursor[]; + /** + * `cursors[0]` is the primary cursor. + * `cursors.slice(1)` are secondary cursors. + */ + private cursors: Cursor[]; // An index which identifies the last cursor that was added / moved (think Ctrl+drag) + // This index refers to `cursors.slice(1)`, i.e. after removing the primary cursor. private lastAddedCursorIndex: number; constructor(context: CursorContext) { this.context = context; - this.primaryCursor = new Cursor(context); - this.secondaryCursors = []; + this.cursors = [new Cursor(context)]; this.lastAddedCursorIndex = 0; } public dispose(): void { - this.primaryCursor.dispose(this.context); - this.killSecondaryCursors(); + for (const cursor of this.cursors) { + cursor.dispose(this.context); + } } public startTrackingSelections(): void { - this.primaryCursor.startTrackingSelection(this.context); - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - this.secondaryCursors[i].startTrackingSelection(this.context); + for (const cursor of this.cursors) { + cursor.startTrackingSelection(this.context); } } public stopTrackingSelections(): void { - this.primaryCursor.stopTrackingSelection(this.context); - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - this.secondaryCursors[i].stopTrackingSelection(this.context); + for (const cursor of this.cursors) { + cursor.stopTrackingSelection(this.context); } } @@ -49,77 +53,43 @@ export class CursorCollection { } public ensureValidState(): void { - this.primaryCursor.ensureValidState(this.context); - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - this.secondaryCursors[i].ensureValidState(this.context); + for (const cursor of this.cursors) { + cursor.ensureValidState(this.context); } } public readSelectionFromMarkers(): Selection[] { - let result: Selection[] = []; - result[0] = this.primaryCursor.readSelectionFromMarkers(this.context); - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - result[i + 1] = this.secondaryCursors[i].readSelectionFromMarkers(this.context); - } - return result; + return this.cursors.map(c => c.readSelectionFromMarkers(this.context)); } public getAll(): CursorState[] { - let result: CursorState[] = []; - result[0] = this.primaryCursor.asCursorState(); - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - result[i + 1] = this.secondaryCursors[i].asCursorState(); - } - return result; + return this.cursors.map(c => c.asCursorState()); } public getViewPositions(): Position[] { - let result: Position[] = []; - result[0] = this.primaryCursor.viewState.position; - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - result[i + 1] = this.secondaryCursors[i].viewState.position; - } - return result; + return this.cursors.map(c => c.viewState.position); } public getTopMostViewPosition(): Position { - let result = this.primaryCursor.viewState.position; - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - const viewPosition = this.secondaryCursors[i].viewState.position; - if (viewPosition.isBefore(result)) { - result = viewPosition; - } - } - return result; + return findMinBy( + this.cursors, + compareBy(c => c.viewState.position, Position.compare) + )!.viewState.position; } public getBottomMostViewPosition(): Position { - let result = this.primaryCursor.viewState.position; - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - const viewPosition = this.secondaryCursors[i].viewState.position; - if (result.isBeforeOrEqual(viewPosition)) { - result = viewPosition; - } - } - return result; + return findMaxBy( + this.cursors, + compareBy(c => c.viewState.position, Position.compare) + )!.viewState.position; } public getSelections(): Selection[] { - let result: Selection[] = []; - result[0] = this.primaryCursor.modelState.selection; - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - result[i + 1] = this.secondaryCursors[i].modelState.selection; - } - return result; + return this.cursors.map(c => c.modelState.selection); } public getViewSelections(): Selection[] { - let result: Selection[] = []; - result[0] = this.primaryCursor.viewState.selection; - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - result[i + 1] = this.secondaryCursors[i].viewState.selection; - } - return result; + return this.cursors.map(c => c.viewState.selection); } public setSelections(selections: ISelection[]): void { @@ -127,14 +97,14 @@ export class CursorCollection { } public getPrimaryCursor(): CursorState { - return this.primaryCursor.asCursorState(); + return this.cursors[0].asCursorState(); } public setStates(states: PartialCursorState[] | null): void { if (states === null) { return; } - this.primaryCursor.setState(this.context, states[0].modelState, states[0].viewState); + this.cursors[0].setState(this.context, states[0].modelState, states[0].viewState); this._setSecondaryStates(states.slice(1)); } @@ -142,7 +112,7 @@ export class CursorCollection { * Creates or disposes secondary cursors as necessary to match the number of `secondarySelections`. */ private _setSecondaryStates(secondaryStates: PartialCursorState[]): void { - const secondaryCursorsLength = this.secondaryCursors.length; + const secondaryCursorsLength = this.cursors.length - 1; const secondaryStatesLength = secondaryStates.length; if (secondaryCursorsLength < secondaryStatesLength) { @@ -153,12 +123,12 @@ export class CursorCollection { } else if (secondaryCursorsLength > secondaryStatesLength) { let removeCnt = secondaryCursorsLength - secondaryStatesLength; for (let i = 0; i < removeCnt; i++) { - this._removeSecondaryCursor(this.secondaryCursors.length - 1); + this._removeSecondaryCursor(this.cursors.length - 2); } } for (let i = 0; i < secondaryStatesLength; i++) { - this.secondaryCursors[i].setState(this.context, secondaryStates[i].modelState, secondaryStates[i].viewState); + this.cursors[i + 1].setState(this.context, secondaryStates[i].modelState, secondaryStates[i].viewState); } } @@ -167,12 +137,12 @@ export class CursorCollection { } private _addSecondaryCursor(): void { - this.secondaryCursors.push(new Cursor(this.context)); - this.lastAddedCursorIndex = this.secondaryCursors.length; + this.cursors.push(new Cursor(this.context)); + this.lastAddedCursorIndex = this.cursors.length - 1; } public getLastAddedCursorIndex(): number { - if (this.secondaryCursors.length === 0 || this.lastAddedCursorIndex === 0) { + if (this.cursors.length === 1 || this.lastAddedCursorIndex === 0) { return 0; } return this.lastAddedCursorIndex; @@ -182,24 +152,15 @@ export class CursorCollection { if (this.lastAddedCursorIndex >= removeIndex + 1) { this.lastAddedCursorIndex--; } - this.secondaryCursors[removeIndex].dispose(this.context); - this.secondaryCursors.splice(removeIndex, 1); - } - - private _getAll(): Cursor[] { - let result: Cursor[] = []; - result[0] = this.primaryCursor; - for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { - result[i + 1] = this.secondaryCursors[i]; - } - return result; + this.cursors[removeIndex + 1].dispose(this.context); + this.cursors.splice(removeIndex + 1, 1); } public normalize(): void { - if (this.secondaryCursors.length === 0) { + if (this.cursors.length === 1) { return; } - let cursors = this._getAll(); + let cursors = [...this.cursors]; interface SortedCursor { index: number; @@ -212,12 +173,8 @@ export class CursorCollection { selection: cursors[i].modelState.selection, }); } - sortedCursors.sort((a, b) => { - if (a.selection.startLineNumber === b.selection.startLineNumber) { - return a.selection.startColumn - b.selection.startColumn; - } - return a.selection.startLineNumber - b.selection.startLineNumber; - }); + + sortedCursors.sort(compareBy(s => s.selection, Range.compareRangesUsingStarts)); for (let sortedCursorIndex = 0; sortedCursorIndex < sortedCursors.length - 1; sortedCursorIndex++) { const current = sortedCursors[sortedCursorIndex]; From 137ea7552bb1dca389a949eae0d91fda89a64062 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 12:51:54 +0100 Subject: [PATCH 0419/2210] findMaxBy -> findLastMaxBy --- src/vs/base/common/arrays.ts | 18 ++++++++++++++++++ src/vs/base/test/common/arrays.test.ts | 9 +++++++++ .../common/controller/cursorCollection.ts | 4 ++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index c387b57d1dd39..d7a2bd450e0ff 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -625,6 +625,24 @@ export function findMaxBy(items: readonly T[], comparator: Comparator): T return max; } +/** + * Returns the last item that is equal to or greater than every other item. +*/ +export function findLastMaxBy(items: readonly T[], comparator: Comparator): T | undefined { + if (items.length === 0) { + return undefined; + } + + let max = items[0]; + for (let i = 1; i < items.length; i++) { + const item = items[i]; + if (comparator(item, max) >= 0) { + max = item; + } + } + return max; +} + /** * Returns the first item that is equal to or less than every other item. */ diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index cb44d799581ea..5975ce0574359 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -347,6 +347,15 @@ suite('Arrays', () => { ); }); + test('findLastMaxBy', () => { + const array = [{ v: 3 }, { v: 5 }, { v: 2 }, { v: 2 }, { v: 2 }, { v: 5 }]; + + assert.strictEqual( + array.indexOf(arrays.findLastMaxBy(array, arrays.compareBy(v => v.v, arrays.numberComparator))!), + 5 + ); + }); + test('findMinBy', () => { const array = [{ v: 3 }, { v: 5 }, { v: 2 }, { v: 2 }, { v: 2 }, { v: 5 }]; diff --git a/src/vs/editor/common/controller/cursorCollection.ts b/src/vs/editor/common/controller/cursorCollection.ts index 2a8f659c2e33b..718b722c22ce8 100644 --- a/src/vs/editor/common/controller/cursorCollection.ts +++ b/src/vs/editor/common/controller/cursorCollection.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareBy, findMaxBy, findMinBy } from 'vs/base/common/arrays'; +import { compareBy, findLastMaxBy, findMinBy } from 'vs/base/common/arrays'; import { CursorContext, CursorState, PartialCursorState } from 'vs/editor/common/controller/cursorCommon'; import { Cursor } from 'vs/editor/common/controller/oneCursor'; import { Position } from 'vs/editor/common/core/position'; @@ -78,7 +78,7 @@ export class CursorCollection { } public getBottomMostViewPosition(): Position { - return findMaxBy( + return findLastMaxBy( this.cursors, compareBy(c => c.viewState.position, Position.compare) )!.viewState.position; From c893442a6ada4be1ee77a6ac46e489f4276fd340 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 13:56:39 +0100 Subject: [PATCH 0420/2210] [...x] -> x.slice(0) --- src/vs/editor/common/controller/cursorCollection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/controller/cursorCollection.ts b/src/vs/editor/common/controller/cursorCollection.ts index 718b722c22ce8..26b358088fd8a 100644 --- a/src/vs/editor/common/controller/cursorCollection.ts +++ b/src/vs/editor/common/controller/cursorCollection.ts @@ -160,7 +160,7 @@ export class CursorCollection { if (this.cursors.length === 1) { return; } - let cursors = [...this.cursors]; + let cursors = this.cursors.slice(0); interface SortedCursor { index: number; From 52e20877aa540907b81fe31effcea1ba5c92b162 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 13:58:03 +0100 Subject: [PATCH 0421/2210] Fix #138272 --- src/vs/base/test/browser/indexedDB.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/base/test/browser/indexedDB.test.ts b/src/vs/base/test/browser/indexedDB.test.ts index 04d41579ba30a..581b67191484f 100644 --- a/src/vs/base/test/browser/indexedDB.test.ts +++ b/src/vs/base/test/browser/indexedDB.test.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IndexedDB } from 'vs/base/browser/indexedDB'; +import { flakySuite } from 'vs/base/test/common/testUtils'; -suite('IndexedDB', () => { +flakySuite('IndexedDB', () => { let indexedDB: IndexedDB; @@ -15,7 +16,9 @@ suite('IndexedDB', () => { }); teardown(() => { - indexedDB.close(); + if (indexedDB) { + indexedDB.close(); + } }); test('runInTransaction', async () => { From 18e82726042cca42b4b71060f9a92917feb8d9bf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Dec 2021 05:10:38 -0800 Subject: [PATCH 0422/2210] Remove resolved TODO Fixes in #128406 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3b3e69ae00f67..407f1fe26a0fc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -2234,7 +2234,6 @@ async function preparePathForShell(originalPath: string, executable: string | un return; } - // TODO: This should use the process manager's OS, not the local OS if (os === OperatingSystem.Windows) { // 17063 is the build number where wsl path was introduced. // Update Windows uriPath to be executed in WSL. From 64562e2a0e37d947ee5e2ea4030931da548b5a7a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Dec 2021 14:13:36 +0100 Subject: [PATCH 0423/2210] smoke - reimplement logging --- test/automation/src/application.ts | 28 ++++----- test/automation/src/code.ts | 37 +++++------ test/automation/src/electronDriver.ts | 17 +++-- test/automation/src/logger.ts | 25 +++++++- test/automation/src/playwrightDriver.ts | 81 +++++++++++------------- test/smoke/src/main.ts | 82 +++++++++++-------------- test/smoke/src/utils.ts | 4 +- test/smoke/test/index.js | 35 ++++++++++- 8 files changed, 171 insertions(+), 138 deletions(-) diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 436c2fd9073ab..4a58db4aa80b9 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -77,8 +77,9 @@ export class Application { private async _start(workspaceOrFolder = this.workspacePathOrFolder, extraArgs: string[] = []): Promise { this._workspacePathOrFolder = workspaceOrFolder; - await this.startApplication(extraArgs); - await this.checkWindowReady(); + + const code = await this.startApplication(extraArgs); + await this.checkWindowReady(code); } async stop(): Promise { @@ -96,9 +97,7 @@ export class Application { const raw = await this.code.capturePage(); const buffer = Buffer.from(raw, 'base64'); const screenshotPath = path.join(this.options.screenshotsPath, `${name}.png`); - if (this.options.log) { - this.logger.log('*** Screenshot recorded:', screenshotPath); - } + this.logger.log('Screenshot recorded:', screenshotPath); fs.writeFileSync(screenshotPath, buffer); } @@ -112,26 +111,23 @@ export class Application { await this._code?.stopTracing(name, persist); } - private async startApplication(extraArgs: string[] = []): Promise { - this._code = await spawn({ + private async startApplication(extraArgs: string[] = []): Promise { + const code = this._code = await spawn({ ...this.options, extraArgs: [...(this.options.extraArgs || []), ...extraArgs], }); this._workbench = new Workbench(this._code, this.userDataPath); - } - private async checkWindowReady(): Promise { - if (!this.code) { - console.error('No code instance found'); - return; - } + return code; + } - await this.code.waitForWindowIds(ids => ids.length > 0); - await this.code.waitForElement('.monaco-workbench'); + private async checkWindowReady(code: Code): Promise { + await code.waitForWindowIds(ids => ids.length > 0); + await code.waitForElement('.monaco-workbench'); if (this.remote) { - await this.code.waitForTextContent('.monaco-workbench .statusbar-item[id="status.host"]', ' TestResolver', undefined, 2000); + await code.waitForTextContent('.monaco-workbench .statusbar-item[id="status.host"]', ' TestResolver', undefined, 2000); } // wait a bit, since focus might be stolen off widgets diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index acf35f9a823dc..0e30e8a1690e9 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -22,7 +22,6 @@ export interface SpawnOptions { logger: Logger; verbose?: boolean; extraArgs?: string[]; - log?: string; remote?: boolean; web?: boolean; headless?: boolean; @@ -52,13 +51,13 @@ export async function spawn(options: SpawnOptions): Promise { } async function spawnBrowser(options: SpawnOptions): Promise { - const { serverProcess, client, driver } = await launchPlaywright(options.codePath, options.userDataDir, options.extensionsPath, options.workspacePath, Boolean(options.verbose), options); + const { serverProcess, client, driver } = await launchPlaywright(options.codePath, options.userDataDir, options.extensionsPath, options.workspacePath, Boolean(options.verbose), options, options.logger); return new Code(client, driver, options.logger, serverProcess); } async function spawnElectron(options: SpawnOptions): Promise { - const { electronProcess, client, driver } = await launchElectron(options.codePath, options.userDataDir, options.extensionsPath, options.workspacePath, Boolean(options.verbose), Boolean(options.remote), options.log, options.extraArgs); + const { electronProcess, client, driver } = await launchElectron(options.codePath, options.userDataDir, options.extensionsPath, options.workspacePath, Boolean(options.verbose), Boolean(options.remote), options.extraArgs, options.logger); return new Code(client, driver, options.logger, electronProcess); } @@ -66,6 +65,7 @@ async function spawnElectron(options: SpawnOptions): Promise { async function poll( fn: () => Thenable, acceptFn: (result: T) => boolean, + logger: Logger, timeoutMessage: string, retryCount: number = 200, retryInterval: number = 100 // millis @@ -75,9 +75,9 @@ async function poll( while (true) { if (trial > retryCount) { - console.error('** Timeout!'); - console.error(lastError); - console.error(`*** Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`); + logger.log('Timeout!'); + logger.log(lastError); + logger.log(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`); throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`); } @@ -144,7 +144,7 @@ export class Code { } async waitForWindowIds(fn: (windowIds: number[]) => boolean): Promise { - await poll(() => this.driver.getWindowIds(), fn, `get window ids`); + await poll(() => this.driver.getWindowIds(), fn, this.logger, `get window ids`); } async dispatchKeybinding(keybinding: string): Promise { @@ -171,7 +171,7 @@ export class Code { retries++; if (retries > 20) { - console.warn('Smoke test exit call did not terminate process after 10s, still trying...'); + this.logger.log('Smoke test exit call did not terminate process after 10s, still trying...'); } if (retries > 40) { @@ -200,6 +200,7 @@ export class Code { return await poll( () => this.driver.getElements(windowId, selector).then(els => els.length > 0 ? Promise.resolve(els[0].textContent) : Promise.reject(new Error('Element not found for textContent'))), s => accept!(typeof s === 'string' ? s : ''), + this.logger, `get text content '${selector}'`, retryCount ); @@ -207,52 +208,52 @@ export class Code { async waitAndClick(selector: string, xoffset?: number, yoffset?: number, retryCount: number = 200): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.click(windowId, selector, xoffset, yoffset), () => true, `click '${selector}'`, retryCount); + await poll(() => this.driver.click(windowId, selector, xoffset, yoffset), () => true, this.logger, `click '${selector}'`, retryCount); } async waitAndDoubleClick(selector: string): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.doubleClick(windowId, selector), () => true, `double click '${selector}'`); + await poll(() => this.driver.doubleClick(windowId, selector), () => true, this.logger, `double click '${selector}'`); } async waitForSetValue(selector: string, value: string): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.setValue(windowId, selector, value), () => true, `set value '${selector}'`); + await poll(() => this.driver.setValue(windowId, selector, value), () => true, this.logger, `set value '${selector}'`); } async waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise { const windowId = await this.getActiveWindowId(); - return await poll(() => this.driver.getElements(windowId, selector, recursive), accept, `get elements '${selector}'`); + return await poll(() => this.driver.getElements(windowId, selector, recursive), accept, this.logger, `get elements '${selector}'`); } async waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result, retryCount: number = 200): Promise { const windowId = await this.getActiveWindowId(); - return await poll(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, `get element '${selector}'`, retryCount); + return await poll(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, this.logger, `get element '${selector}'`, retryCount); } async waitForActiveElement(selector: string, retryCount: number = 200): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.isActiveElement(windowId, selector), r => r, `is active element '${selector}'`, retryCount); + await poll(() => this.driver.isActiveElement(windowId, selector), r => r, this.logger, `is active element '${selector}'`, retryCount); } async waitForTitle(fn: (title: string) => boolean): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.getTitle(windowId), fn, `get title`); + await poll(() => this.driver.getTitle(windowId), fn, this.logger, `get title`); } async waitForTypeInEditor(selector: string, text: string): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.typeInEditor(windowId, selector, text), () => true, `type in editor '${selector}'`); + await poll(() => this.driver.typeInEditor(windowId, selector, text), () => true, this.logger, `type in editor '${selector}'`); } async waitForTerminalBuffer(selector: string, accept: (result: string[]) => boolean): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.getTerminalBuffer(windowId, selector), accept, `get terminal buffer '${selector}'`); + await poll(() => this.driver.getTerminalBuffer(windowId, selector), accept, this.logger, `get terminal buffer '${selector}'`); } async writeInTerminal(selector: string, value: string): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.writeInTerminal(windowId, selector, value), () => true, `writeInTerminal '${selector}'`); + await poll(() => this.driver.writeInTerminal(windowId, selector, value), () => true, this.logger, `writeInTerminal '${selector}'`); } async getLocaleInfo(): Promise { diff --git a/test/automation/src/electronDriver.ts b/test/automation/src/electronDriver.ts index 92fac90349a64..51f59af2b8dfc 100644 --- a/test/automation/src/electronDriver.ts +++ b/test/automation/src/electronDriver.ts @@ -13,10 +13,11 @@ import { promisify } from 'util'; import * as kill from 'tree-kill'; import { copyExtension } from './extensions'; import { URI } from 'vscode-uri'; +import { Logger } from './logger'; const repoPath = path.join(__dirname, '../../..'); -export async function launch(codePath: string | undefined, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, remote: boolean, log: string | undefined, extraArgs: string[] | undefined): Promise<{ electronProcess: ChildProcess, client: IDisposable, driver: IDriver }> { +export async function launch(codePath: string | undefined, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, remote: boolean, extraArgs: string[] | undefined, logger: Logger): Promise<{ electronProcess: ChildProcess, client: IDisposable, driver: IDriver }> { const env = { ...process.env }; const logsPath = path.join(repoPath, '.build', 'logs', remote ? 'smoke-tests-remote' : 'smoke-tests'); const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); @@ -79,10 +80,6 @@ export async function launch(codePath: string | undefined, userDataDir: string, spawnOptions.stdio = ['ignore', 'inherit', 'inherit']; } - if (log) { - args.push('--log', log); - } - if (extraArgs) { args.push(...extraArgs); } @@ -91,13 +88,13 @@ export async function launch(codePath: string | undefined, userDataDir: string, const electronProcess = spawn(electronPath, args, spawnOptions); if (verbose) { - console.info(`*** Started electron for desktop smoke tests on pid ${electronProcess.pid}`); + logger.log(`Started electron for desktop smoke tests on pid ${electronProcess.pid}`); } let electronProcessDidExit = false; electronProcess.once('exit', (code, signal) => { if (verbose) { - console.info(`*** Electron for desktop smoke tests terminated (pid: ${electronProcess.pid}, code: ${code}, signal: ${signal})`); + logger.log(`Electron for desktop smoke tests terminated (pid: ${electronProcess.pid}, code: ${code}, signal: ${signal})`); } electronProcessDidExit = true; }); @@ -118,12 +115,12 @@ export async function launch(codePath: string | undefined, userDataDir: string, // give up if (++retries > 30) { - console.error(`*** Error connecting driver: ${err}. Giving up...`); + logger.log(`Error connecting driver: ${err}. Giving up...`); try { await promisify(kill)(electronProcess.pid!); } catch (error) { - console.warn(`*** Error tearing down electron client (pid: ${electronProcess.pid}): ${error}`); + logger.log(`Error tearing down electron client (pid: ${electronProcess.pid}): ${error}`); } throw err; @@ -132,7 +129,7 @@ export async function launch(codePath: string | undefined, userDataDir: string, // retry else { if ((err as NodeJS.ErrnoException).code !== 'ENOENT' /* ENOENT is expected for as long as the server has not started on the socket */) { - console.error(`*** Error connecting driver: ${err}. Attempting to retry...`); + logger.log(`Error connecting driver: ${err}. Attempting to retry...`); } await new Promise(resolve => setTimeout(resolve, 1000)); diff --git a/test/automation/src/logger.ts b/test/automation/src/logger.ts index b36b502d6aead..9320bbca7bd9a 100644 --- a/test/automation/src/logger.ts +++ b/test/automation/src/logger.ts @@ -39,4 +39,27 @@ export class MultiLogger implements Logger { logger.log(message, ...args); } } -} \ No newline at end of file +} + +export async function measureAndLog(promise: Promise, name: string, logger: Logger): Promise { + const now = Date.now(); + + logger.log(`Starting operation '${name}...`); + + let res: T | undefined = undefined; + let e: unknown; + try { + res = await promise; + } catch (error) { + e = error; + } + + if (e) { + logger.log(`Finished operation '${name}' with error ${e} after ${Date.now() - now}ms`); + throw e; + } + + logger.log(`Finished operation '${name}' successfully after ${Date.now() - now}ms`); + + return res as unknown as T; +} diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index b1a5db3b508ae..73f7543260ef7 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -12,6 +12,8 @@ import { IDriver, IDisposable, IWindowDriver } from './driver'; import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; import { PageFunction } from 'playwright-core/types/structs'; +import { Logger } from './logger'; +import { measureAndLog } from '.'; const width = 1200; const height = 800; @@ -43,7 +45,8 @@ class PlaywrightDriver implements IDriver { private readonly server: ChildProcess, private readonly browser: playwright.Browser, private readonly context: playwright.BrowserContext, - private readonly page: playwright.Page + private readonly page: playwright.Page, + private readonly logger: Logger ) { } @@ -57,9 +60,9 @@ class PlaywrightDriver implements IDriver { async startTracing(windowId: number, name: string): Promise { try { - await this.warnAfter(this.context.tracing.startChunk({ title: name }), 5000, 'Starting playwright trace took more than 5 seconds'); + await measureAndLog(this.context.tracing.startChunk({ title: name }), `startTracing for ${name}`, this.logger); } catch (error) { - console.warn(`Failed to start playwright tracing (chunk): ${error}`); + // Ignore } } @@ -70,9 +73,9 @@ class PlaywrightDriver implements IDriver { persistPath = join(logsPath, `playwright-trace-${traceCounter++}-${name.replace(/\s+/g, '-')}.zip`); } - await this.warnAfter(this.context.tracing.stopChunk({ path: persistPath }), 5000, 'Stopping playwright trace took more than 5 seconds'); + await measureAndLog(this.context.tracing.stopChunk({ path: persistPath }), `stopTracing for ${name}`, this.logger); } catch (error) { - console.warn(`Failed to stop playwright tracing (chunk): ${error}`); + // Ignore } } @@ -82,32 +85,22 @@ class PlaywrightDriver implements IDriver { async exitApplication() { try { - await this.warnAfter(this.context.tracing.stop(), 5000, 'Stopping playwright trace took >5seconds'); + await measureAndLog(this.context.tracing.stop(), 'stop tracing', this.logger); } catch (error) { - console.warn(`Failed to stop playwright tracing: ${error}`); + // Ignore } try { - await this.warnAfter(this.browser.close(), 5000, 'Closing playwright browser took >5seconds'); + await measureAndLog(this.browser.close(), 'Browser.close()', this.logger); } catch (error) { - console.warn(`Failed to close browser: ${error}`); + // Ignore } - await this.warnAfter(teardown(this.server), 5000, 'Tearing down server took >5seconds'); + await measureAndLog(teardown(this.server, this.logger), 'teardown server', this.logger); return false; } - private async warnAfter(promise: Promise, delay: number, msg: string): Promise { - const timeout = setTimeout(() => console.warn(msg), delay); - - try { - await promise; - } finally { - clearTimeout(timeout); - } - } - async dispatchKeybinding(windowId: number, keybinding: string) { const chords = keybinding.split(' '); for (let i = 0; i < chords.length; i++) { @@ -211,24 +204,24 @@ export interface PlaywrightOptions { readonly headless?: boolean; } -export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, options: PlaywrightOptions = {}): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver }> { +export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, options: PlaywrightOptions = {}, logger: Logger): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver }> { // Launch server - const { serverProcess, endpoint } = await launchServer(userDataDir, codeServerPath, extensionsPath, verbose); + const { serverProcess, endpoint } = await launchServer(userDataDir, codeServerPath, extensionsPath, verbose, logger); // Launch browser - const { browser, context, page } = await launchBrowser(options, endpoint, workspacePath); + const { browser, context, page } = await launchBrowser(options, endpoint, workspacePath, logger); return { serverProcess, client: { dispose: () => { /* there is no client to dispose for browser, teardown is triggered via exitApplication call */ } }, - driver: new PlaywrightDriver(serverProcess, browser, context, page) + driver: new PlaywrightDriver(serverProcess, browser, context, page, logger) }; } -async function launchServer(userDataDir: string, codeServerPath: string | undefined, extensionsPath: string, verbose: boolean) { +async function launchServer(userDataDir: string, codeServerPath: string | undefined, extensionsPath: string, verbose: boolean, logger: Logger) { const agentFolder = userDataDir; await promisify(mkdir)(agentFolder); const env = { @@ -245,16 +238,16 @@ async function launchServer(userDataDir: string, codeServerPath: string | undefi args.push(`--logsPath=${logsPath}`); if (verbose) { - console.log(`Starting built server from '${serverLocation}'`); - console.log(`Storing log files into '${logsPath}'`); + logger.log(`Starting built server from '${serverLocation}'`); + logger.log(`Storing log files into '${logsPath}'`); } } else { serverLocation = join(root, `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`); args.push('--logsPath', logsPath); if (verbose) { - console.log(`Starting server out of sources from '${serverLocation}'`); - console.log(`Storing log files into '${logsPath}'`); + logger.log(`Starting server out of sources from '${serverLocation}'`); + logger.log(`Storing log files into '${logsPath}'`); } } @@ -265,16 +258,16 @@ async function launchServer(userDataDir: string, codeServerPath: string | undefi ); if (verbose) { - console.info(`*** Started server for browser smoke tests (pid: ${serverProcess.pid})`); - serverProcess.once('exit', (code, signal) => console.info(`*** Server for browser smoke tests terminated (pid: ${serverProcess.pid}, code: ${code}, signal: ${signal})`)); + logger.log(`*** Started server for browser smoke tests (pid: ${serverProcess.pid})`); + serverProcess.once('exit', (code, signal) => logger.log(`Server for browser smoke tests terminated (pid: ${serverProcess.pid}, code: ${code}, signal: ${signal})`)); - serverProcess.stderr?.on('data', error => console.log(`Server stderr: ${error}`)); - serverProcess.stdout?.on('data', data => console.log(`Server stdout: ${data}`)); + serverProcess.stderr?.on('data', error => logger.log(`Server stderr: ${error}`)); + serverProcess.stdout?.on('data', data => logger.log(`Server stdout: ${data}`)); } - process.on('exit', () => teardown(serverProcess)); - process.on('SIGINT', () => teardown(serverProcess)); - process.on('SIGTERM', () => teardown(serverProcess)); + process.on('exit', () => teardown(serverProcess, logger)); + process.on('SIGINT', () => teardown(serverProcess, logger)); + process.on('SIGTERM', () => teardown(serverProcess, logger)); return { serverProcess, @@ -282,24 +275,24 @@ async function launchServer(userDataDir: string, codeServerPath: string | undefi }; } -async function launchBrowser(options: PlaywrightOptions, endpoint: string, workspacePath: string) { +async function launchBrowser(options: PlaywrightOptions, endpoint: string, workspacePath: string, logger: Logger) { const browser = await playwright[options.browser ?? 'chromium'].launch({ headless: options.headless ?? false }); const context = await browser.newContext(); try { await context.tracing.start({ screenshots: true, snapshots: true, sources: true }); } catch (error) { - console.warn(`Failed to start playwright tracing: ${error}`); // do not fail the build when this fails + logger.log(`Failed to start playwright tracing: ${error}`); // do not fail the build when this fails } const page = await context.newPage(); await page.setViewportSize({ width, height }); - page.on('pageerror', async (error) => console.error(`Playwright ERROR: page error: ${error}`)); - page.on('crash', page => console.error('Playwright ERROR: page crash')); + page.on('pageerror', async (error) => logger.log(`Playwright ERROR: page error: ${error}`)); + page.on('crash', page => logger.log('Playwright ERROR: page crash')); page.on('response', async (response) => { if (response.status() >= 400) { - console.error(`Playwright ERROR: HTTP status ${response.status()} for ${response.url()}`); + logger.log(`Playwright ERROR: HTTP status ${response.status()} for ${response.url()}`); } }); @@ -309,7 +302,7 @@ async function launchBrowser(options: PlaywrightOptions, endpoint: string, works return { browser, context, page }; } -async function teardown(server: ChildProcess): Promise { +async function teardown(server: ChildProcess, logger: Logger): Promise { const serverPid = server.pid; if (typeof serverPid !== 'number') { return; @@ -324,14 +317,14 @@ async function teardown(server: ChildProcess): Promise { } catch (error) { try { process.kill(serverPid, 0); // throws an exception if the process doesn't exist anymore - console.warn(`Error tearing down server (pid: ${serverPid}, attempt: ${retries}): ${error}`); + logger.log(`Error tearing down server (pid: ${serverPid}, attempt: ${retries}): ${error}`); } catch (error) { return; // Expected when process is gone } } } - console.error(`Gave up tearing down server after ${retries} attempts...`); + logger.log(`Gave up tearing down server after ${retries} attempts...`); } function waitForEndpoint(server: ChildProcess): Promise { diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index b66acfa5aae9b..ce06c48fff80d 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import { promisify } from 'util'; import { gracefulify } from 'graceful-fs'; import * as cp from 'child_process'; import * as path from 'path'; @@ -12,7 +11,6 @@ import * as os from 'os'; import * as minimist from 'minimist'; import * as rimraf from 'rimraf'; import * as mkdirp from 'mkdirp'; -import { ncp } from 'ncp'; import * as vscodetest from 'vscode-test'; import fetch from 'node-fetch'; import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger } from '../../automation'; @@ -59,7 +57,6 @@ const opts = minimist(args, { 'wait-time', 'test-repo', 'screenshots', - 'log', 'electronArgs' ], boolean: [ @@ -209,9 +206,9 @@ else { const userDataDir = path.join(testDataPath, 'd'); -async function setupRepository(): Promise { +async function setupRepository(logger: Logger): Promise { if (opts['test-repo']) { - console.log('*** Copying test project repository:', opts['test-repo']); + logger.log('Copying test project repository:', opts['test-repo']); rimraf.sync(workspacePath); // not platform friendly if (process.platform === 'win32') { @@ -222,10 +219,10 @@ async function setupRepository(): Promise { } else { if (!fs.existsSync(workspacePath)) { - console.log('*** Cloning test project repository...'); + logger.log('Cloning test project repository...'); cp.spawnSync('git', ['clone', testRepoUrl, workspacePath]); } else { - console.log('*** Cleaning test project repository...'); + logger.log('Cleaning test project repository...'); cp.spawnSync('git', ['fetch'], { cwd: workspacePath }); cp.spawnSync('git', ['reset', '--hard', 'FETCH_HEAD'], { cwd: workspacePath }); cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath }); @@ -233,12 +230,12 @@ async function setupRepository(): Promise { // None of the current smoke tests have a dependency on the packages. // If new smoke tests are added that need the packages, uncomment this. - // console.log('*** Running yarn...'); + // logger.log('Running yarn...'); // cp.execSync('yarn', { cwd: workspacePath, stdio: 'inherit' }); } } -async function ensureStableCode(): Promise { +async function ensureStableCode(logger: Logger): Promise { if (opts.web || !opts['build']) { return; } @@ -261,7 +258,7 @@ async function ensureStableCode(): Promise { throw new Error(`Could not find suitable stable version ${majorMinorVersion}`); } - console.log(`*** Found VS Code v${version}, downloading previous VS Code version ${previousVersion.version}...`); + logger.log(`Found VS Code v${version}, downloading previous VS Code version ${previousVersion.version}...`); const stableCodeExecutable = await vscodetest.download({ cachePath: path.join(os.tmpdir(), 'vscode-test'), @@ -281,40 +278,22 @@ async function ensureStableCode(): Promise { throw new Error(`Can't find Stable VSCode at ${stableCodePath}.`); } - console.log(`*** Using stable build ${stableCodePath} for migration tests`); + logger.log(`Using stable build ${stableCodePath} for migration tests`); opts['stable-build'] = stableCodePath; } -async function setup(): Promise { - console.log('*** Test data:', testDataPath); - console.log('*** Preparing smoketest setup...'); +async function setup(logger: Logger): Promise { + logger.log('Test data:', testDataPath); + logger.log('Preparing smoketest setup...'); - await ensureStableCode(); - await setupRepository(); + await ensureStableCode(logger); + await setupRepository(logger); - console.log('*** Smoketest setup done!\n'); + logger.log('Smoketest setup done!\n'); } async function createOptions(): Promise { - const loggers: Logger[] = []; - - if (opts.verbose) { - loggers.push(new ConsoleLogger()); - } - - const log: string | undefined = 'trace'; // because smoke tests are flaky - let logsPath: string; - if (opts.log) { - logsPath = opts.log; - } else { - logsPath = path.join(repoPath, '.build', 'logs', opts.web ? 'smoke-tests-browser' : opts.remote ? 'smoke-tests-remote' : 'smoke-tests', 'smoke-test-runner.log'); - } - - await mkdirp(path.dirname(logsPath)); - - loggers.push(new FileLogger(logsPath)); - return { quality, codePath: opts.build, @@ -322,9 +301,8 @@ async function createOptions(): Promise { userDataDir, extensionsPath, waitTime: parseInt(opts['wait-time'] || '0') || 20, - logger: new MultiLogger(loggers), + logger: await createLogger(), verbose: opts.verbose, - log, screenshotsPath, remote: opts.remote, web: opts.web, @@ -334,19 +312,31 @@ async function createOptions(): Promise { }; } + +async function createLogger(): Promise { + const loggers: Logger[] = []; + + // Log to console if verbose + if (opts.verbose) { + loggers.push(new ConsoleLogger()); + } + + // Always log to log file + const logPath = path.join(repoPath, '.build', 'logs', opts.web ? 'smoke-tests-browser' : opts.remote ? 'smoke-tests-remote' : 'smoke-tests'); + await mkdirp(logPath); + loggers.push(new FileLogger(path.join(logPath, 'smoke-test-runner.log'))); + + return new MultiLogger(loggers); +} + before(async function () { this.timeout(2 * 60 * 1000); // allow two minutes for setup - await setup(); - this.defaultOptions = await createOptions(); + const options = this.defaultOptions = await createOptions(); + + await setup(options.logger); }); after(async function () { - if (opts.log) { - const logsDir = path.join(userDataDir, 'logs'); - const destLogsDir = path.join(path.dirname(opts.log), 'logs'); - await promisify(ncp)(logsDir, destLogsDir); - } - try { // TODO@tyriar TODO@meganrogge lately deleting the test root // folder results in timeouts of 60s or EPERM issues which @@ -376,7 +366,7 @@ after(async function () { }) ]); } catch (error) { - console.error(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`); + this.options.logger(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`); } }); diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index 134c9d236c097..76f465d50814b 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -58,8 +58,8 @@ export async function startApp(args: minimist.ParsedArgs, options: ApplicationOp await app.start(); - if (args.log && options.testTitle) { - app.logger.log('*** Test start:', options.testTitle); + if (options.testTitle) { + app.logger.log('Test start:', options.testTitle); } return app; diff --git a/test/smoke/test/index.js b/test/smoke/test/index.js index 6d07772193379..93d1a8a55cb18 100644 --- a/test/smoke/test/index.js +++ b/test/smoke/test/index.js @@ -35,4 +35,37 @@ if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { const mocha = new Mocha(options); mocha.addFile('out/main.js'); -mocha.run(failures => process.exit(failures ? -1 : 0)); +mocha.run(failures => { + + // Indicate location of log files for further diagnosis + if (failures) { + const repoPath = path.join(__dirname, '..', '..', '..'); + const logPath = path.join(repoPath, '.build', 'logs', opts.web ? 'smoke-tests-browser' : opts.remote ? 'smoke-tests-remote' : 'smoke-tests'); + const logFile = path.join(logPath, 'smoke-test-runner.log'); + + if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + console.log(` +################################################################### +# # +# Logs are attached as build artefact and can be downloaded # +# from the build Summary page (Summary -> Related -> N published) # +# # +################################################################### + `); + } else { + console.log(` +############################################# +# +# Log files of client & server are stored into +# '${logPath}'. +# +# Logs of the smoke test runner are stored into +# '${logFile}'. +# +############################################# + `); + } + } + + process.exit(failures ? -1 : 0); +}); From 91b97cec911a954bcd83d2613bb6d7b6bd4023ad Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 14:36:00 +0100 Subject: [PATCH 0424/2210] Debt: Rename `IModeService` to `ILanguageService` --- build/monaco/monaco.d.ts.recipe | 2 +- .../editor/browser/core/markdownRenderer.ts | 10 ++-- src/vs/editor/browser/widget/diffReview.ts | 6 +-- src/vs/editor/common/model/textModel.ts | 10 ++-- .../modes/languageConfigurationRegistry.ts | 12 ++--- src/vs/editor/common/modes/modesRegistry.ts | 2 +- .../editor/common/services/getIconClasses.ts | 12 ++--- .../{modeService.ts => languageService.ts} | 4 +- .../common/services/languagesRegistry.ts | 2 +- .../editor/common/services/modeServiceImpl.ts | 4 +- src/vs/editor/common/services/modelService.ts | 2 +- .../common/services/modelServiceImpl.ts | 12 ++--- .../services/semanticTokensProviderStyling.ts | 6 +-- .../textResourceConfigurationServiceImpl.ts | 6 +-- .../comment/test/lineCommentCommand.test.ts | 6 +-- .../link/goToDefinitionAtPosition.ts | 6 +-- .../gotoSymbol/peek/referencesWidget.ts | 6 +-- src/vs/editor/contrib/hover/hover.ts | 6 +-- .../contrib/hover/markdownHoverParticipant.ts | 10 ++-- .../editor/contrib/hover/modesGlyphHover.ts | 6 +-- .../inlineCompletions/ghostTextWidget.ts | 6 +-- .../inlineCompletionsHoverParticipant.ts | 6 +-- .../parameterHints/parameterHintsWidget.ts | 6 +-- .../contrib/suggest/suggestWidgetRenderer.ts | 12 ++--- .../contrib/suggest/test/suggestModel.test.ts | 6 +-- .../unicodeHighlighter/unicodeHighlighter.ts | 6 +-- src/vs/editor/standalone/browser/colorizer.ts | 14 +++--- .../browser/inspectTokens/inspectTokens.ts | 20 ++++---- .../browser/standaloneCodeEditor.ts | 12 ++--- .../standalone/browser/standaloneEditor.ts | 16 +++---- .../standalone/browser/standaloneLanguages.ts | 20 ++++---- .../standalone/browser/standaloneServices.ts | 8 ++-- .../standalone/common/monarch/monarchLexer.ts | 30 ++++++------ .../test/browser/standaloneLanguages.test.ts | 6 +-- .../standalone/test/monarch/monarch.test.ts | 40 ++++++++-------- .../test/browser/controller/cursor.test.ts | 12 ++--- src/vs/editor/test/browser/testCodeEditor.ts | 4 +- src/vs/editor/test/common/editorTestUtils.ts | 4 +- src/vs/editor/test/common/mocks/mockMode.ts | 2 +- .../bracketPairColorizer/tokenizer.test.ts | 6 +-- src/vs/editor/test/common/model/model.test.ts | 6 +-- .../common/model/textModelWithTokens.test.ts | 8 ++-- .../test/common/services/modelService.test.ts | 10 ++-- .../textResourceConfigurationService.test.ts | 4 +- .../mainThreadDocumentContentProviders.ts | 6 +-- .../api/browser/mainThreadLanguageFeatures.ts | 12 ++--- .../api/browser/mainThreadLanguages.ts | 14 +++--- .../api/browser/mainThreadNotebookKernels.ts | 12 ++--- .../browser/actions/windowActions.ts | 20 ++++---- .../browser/actions/workspaceCommands.ts | 6 +-- src/vs/workbench/browser/labels.ts | 14 +++--- .../browser/parts/editor/editorQuickAccess.ts | 18 ++++---- .../browser/parts/editor/editorStatus.ts | 30 ++++++------ .../parts/editor/textResourceEditor.ts | 8 ++-- .../common/editor/textEditorModel.ts | 18 ++++---- .../common/editor/textResourceEditorModel.ts | 6 +-- src/vs/workbench/common/resources.ts | 8 ++-- .../browser/preview/bulkEditPreview.ts | 8 ++-- .../bulkEdit/browser/preview/bulkEditTree.ts | 6 +-- .../inspectEditorTokens.ts | 18 ++++---- .../languageConfigurationExtensionPoint.ts | 8 ++-- .../contrib/comments/browser/commentNode.ts | 6 +-- .../comments/browser/commentThreadWidget.ts | 8 ++-- .../debug/browser/debugAdapterManager.ts | 6 +-- .../debug/common/debugContentProvider.ts | 8 ++-- .../extensions/browser/extensionEditor.ts | 6 +-- .../browser/extensionsWorkbenchService.ts | 8 ++-- .../browser/fileBasedRecommendations.ts | 6 +-- .../contrib/files/browser/fileActions.ts | 6 +-- .../workbench/contrib/files/common/files.ts | 8 ++-- .../format/browser/formatActionsMultiple.ts | 14 +++--- .../interactive/browser/interactiveEditor.ts | 10 ++-- .../browser/markdownDocumentRenderer.ts | 10 ++-- .../cellStatusBar/statusBarProviders.ts | 8 ++-- .../contrib/codeRenderer/codeRenderer.ts | 12 ++--- .../browser/contrib/find/test/find.test.ts | 4 +- .../undoRedo/test/notebookUndoRedo.test.ts | 14 +++--- .../browser/controller/cellOperations.ts | 6 +-- .../browser/controller/editActions.ts | 16 +++---- .../browser/controller/executeActions.ts | 12 ++--- .../browser/controller/insertCellActions.ts | 16 +++---- .../notebook/browser/diff/diffComponents.ts | 26 +++++------ .../notebook/browser/notebook.contribution.ts | 16 +++---- .../browser/view/cellParts/codeCell.ts | 6 +-- .../browser/view/cellParts/markdownCell.ts | 6 +-- .../view/renderers/backLayerWebView.ts | 10 ++-- .../common/model/notebookCellTextModel.ts | 14 +++--- .../common/model/notebookTextModel.ts | 8 ++-- .../test/browser/notebookCommon.test.ts | 12 ++--- .../test/browser/notebookSelection.test.ts | 10 ++-- .../test/browser/notebookTextModel.test.ts | 22 ++++----- .../test/browser/notebookViewModel.test.ts | 8 ++-- .../test/browser/testNotebookEditor.ts | 8 ++-- .../output/common/outputChannelModel.ts | 10 ++-- .../performance/browser/perfviewEditor.ts | 6 +-- .../preferences/browser/preferencesActions.ts | 16 +++---- .../common/preferencesContribution.ts | 6 +-- .../contrib/scm/browser/scmViewPane.ts | 6 +-- .../search/browser/anythingQuickAccess.ts | 6 +-- .../contrib/search/browser/replaceService.ts | 6 +-- .../searchEditor/browser/searchEditorModel.ts | 26 +++++------ .../snippets/browser/configureSnippets.ts | 16 +++---- .../contrib/snippets/browser/insertSnippet.ts | 8 ++-- .../browser/snippetCompletionProvider.ts | 8 ++-- .../snippets/browser/snippetsService.ts | 16 +++---- .../test/browser/snippetsService.test.ts | 46 +++++++++---------- .../browser/languageSurveys.contribution.ts | 10 ++-- .../testing/common/testingContentProvider.ts | 6 +-- .../browser/themes.test.contribution.ts | 6 +-- .../update/browser/releaseNotesEditor.ts | 6 +-- .../userDataSync/browser/userDataSync.ts | 6 +-- .../gettingStarted/browser/gettingStarted.ts | 8 ++-- .../common/walkThroughContentProvider.ts | 8 ++-- .../electron-sandbox/actions/windowActions.ts | 6 +-- .../browser/abstractFileDialogService.ts | 8 ++-- .../dialogs/browser/simpleFileDialog.ts | 8 ++-- .../electron-sandbox/fileDialogService.ts | 6 +-- .../fileDialogService.test.ts | 6 +-- .../common/workspaceExtensionsConfig.ts | 6 +-- .../test/browser/keybindingEditing.test.ts | 4 +- .../languageDetectionWorkerServiceImpl.ts | 6 +-- .../mode/common/workbenchModeService.ts | 4 +- .../model/common/workbenchModelService.ts | 6 +-- .../preferences/browser/preferencesService.ts | 10 ++-- .../browser/abstractTextMateService.ts | 24 +++++----- .../electron-sandbox/textMateService.ts | 8 ++-- .../browser/browserTextFileService.ts | 6 +-- .../textfile/browser/textFileService.ts | 10 ++-- .../textfile/common/textFileEditorModel.ts | 8 ++-- .../electron-sandbox/nativeTextFileService.ts | 6 +-- .../browser/textModelResolverService.test.ts | 4 +- .../common/untitledTextEditorModel.ts | 6 +-- .../workbench/test/browser/codeeditor.test.ts | 4 +- .../parts/editor/editorDiffModel.test.ts | 2 +- .../browser/parts/editor/editorModel.test.ts | 8 ++-- .../editor/textResourceEditorInput.test.ts | 10 ++-- .../test/browser/workbenchTestServices.ts | 10 ++-- .../test/electron-browser/testing.ts | 2 +- .../electron-browser/workbenchTestServices.ts | 6 +-- 139 files changed, 673 insertions(+), 673 deletions(-) rename src/vs/editor/common/services/{modeService.ts => languageService.ts} (94%) diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index cafc392ad47c3..13814c908f7f0 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -88,7 +88,7 @@ declare namespace monaco.languages { #includeAll(vs/editor/standalone/browser/standaloneLanguages;modes.=>;editorCommon.=>editor.;model.=>editor.;IMarkerData=>editor.IMarkerData): #includeAll(vs/editor/common/modes/languageConfiguration): #includeAll(vs/editor/common/modes;editorCommon.IRange=>IRange;editorCommon.IPosition=>IPosition;editorCommon.=>editor.;IMarkerData=>editor.IMarkerData;model.=>editor.): -#include(vs/editor/common/services/modeService): ILanguageExtensionPoint +#include(vs/editor/common/services/languageService): ILanguageExtensionPoint #includeAll(vs/editor/standalone/common/monarch/monarchTypes): } diff --git a/src/vs/editor/browser/core/markdownRenderer.ts b/src/vs/editor/browser/core/markdownRenderer.ts index 4d14c34867b2c..f89e75f64b3ce 100644 --- a/src/vs/editor/browser/core/markdownRenderer.ts +++ b/src/vs/editor/browser/core/markdownRenderer.ts @@ -6,7 +6,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { renderMarkdown, MarkdownRenderOptions, MarkedOptions } from 'vs/base/browser/markdownRenderer'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -44,7 +44,7 @@ export class MarkdownRenderer { constructor( private readonly _options: IMarkdownRendererOptions, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IOpenerService private readonly _openerService: IOpenerService, ) { } @@ -75,19 +75,19 @@ export class MarkdownRenderer { // it is possible no alias is given in which case we fall back to the current editor lang let languageId: string | undefined | null; if (languageAlias) { - languageId = this._modeService.getModeIdForLanguageName(languageAlias); + languageId = this._languageService.getModeIdForLanguageName(languageAlias); } else if (this._options.editor) { languageId = this._options.editor.getModel()?.getLanguageId(); } if (!languageId) { languageId = 'plaintext'; } - this._modeService.triggerMode(languageId); + this._languageService.triggerMode(languageId); const tokenization = await TokenizationRegistry.getPromise(languageId) ?? undefined; const element = document.createElement('span'); - element.innerHTML = (MarkdownRenderer._ttpTokenizer?.createHTML(value, this._modeService.languageIdCodec, tokenization) ?? tokenizeToString(value, this._modeService.languageIdCodec, tokenization)) as string; + element.innerHTML = (MarkdownRenderer._ttpTokenizer?.createHTML(value, this._languageService.languageIdCodec, tokenization) ?? tokenizeToString(value, this._languageService.languageIdCodec, tokenization)) as string; // use "good" font if (this._options.editor) { diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 11abf7409b874..e56b1cf828508 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -33,7 +33,7 @@ import { Constants } from 'vs/base/common/uint'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ILanguageIdCodec } from 'vs/editor/common/modes'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; const DIFF_LINES_PADDING = 3; @@ -96,7 +96,7 @@ export class DiffReview extends Disposable { constructor( diffEditor: DiffEditorWidget, - @IModeService private readonly _modeService: IModeService + @ILanguageService private readonly _languageService: ILanguageService ) { super(); this._diffEditor = diffEditor; @@ -629,7 +629,7 @@ export class DiffReview extends Disposable { let modLine = minModifiedLine; for (let i = 0, len = diffs.length; i < len; i++) { const diffEntry = diffs[i]; - DiffReview._renderSection(container, diffEntry, modLine, lineHeight, this._width, originalOptions, originalModel, originalModelOpts, modifiedOptions, modifiedModel, modifiedModelOpts, this._modeService.languageIdCodec); + DiffReview._renderSection(container, diffEntry, modLine, lineHeight, this._width, originalOptions, originalModel, originalModelOpts, modifiedOptions, modifiedModel, modifiedModelOpts, this._languageService.languageIdCodec); if (diffEntry.modifiedLineStart !== 0) { modLine = diffEntry.modifiedLineEnd; } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 194bbe874f8b7..0ff0136d89eaa 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -40,7 +40,7 @@ import { getWordAtText } from 'vs/editor/common/model/wordHelper'; import { FormattingOptions, StandardTokenType } from 'vs/editor/common/modes'; import { ILanguageConfigurationService, ResolvedLanguageConfiguration } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_MODE_ID } from 'vs/editor/common/modes/nullMode'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRedo/common/undoRedo'; @@ -328,7 +328,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati languageId: string | null, associatedResource: URI | null = null, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, ) { super(); @@ -398,9 +398,9 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati this._isRedoing = false; this._trimAutoWhitespaceLines = null; - this._tokens = new TokensStore(this._modeService.languageIdCodec); - this._tokens2 = new TokensStore2(this._modeService.languageIdCodec); - this._tokenization = new TextModelTokenization(this, this._modeService.languageIdCodec); + this._tokens = new TokensStore(this._languageService.languageIdCodec); + this._tokens2 = new TokensStore2(this._languageService.languageIdCodec); + this._tokenization = new TextModelTokenization(this, this._languageService.languageIdCodec); this._bracketPairColorizer = this._register(new BracketPairsTextModelPart(this, this._languageConfigurationService)); this._guidesTextModelPart = this._register(new GuidesTextModelPart(this, this._languageConfigurationService)); diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index f5cf6a029edbf..735247bdcdbd5 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -20,7 +20,7 @@ import { RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBracke import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; /** @@ -72,7 +72,7 @@ export class LanguageConfigurationService extends Disposable implements ILanguag constructor( @IConfigurationService private readonly configurationService: IConfigurationService, - @IModeService private readonly modeService: IModeService + @ILanguageService private readonly languageService: ILanguageService ) { super(); @@ -86,7 +86,7 @@ export class LanguageConfigurationService extends Disposable implements ILanguag .filter(([overrideLangName, keys]) => keys.some((k) => languageConfigKeys.has(k)) ) - .map(([overrideLangName]) => this.modeService.validateLanguageId(overrideLangName)); + .map(([overrideLangName]) => this.languageService.validateLanguageId(overrideLangName)); if (globalConfigChanged) { this.configurations.clear(); @@ -110,7 +110,7 @@ export class LanguageConfigurationService extends Disposable implements ILanguag public getLanguageConfiguration(languageId: string): ResolvedLanguageConfiguration { let result = this.configurations.get(languageId); if (!result) { - result = computeConfig(languageId, this.configurationService, this.modeService); + result = computeConfig(languageId, this.configurationService, this.languageService); this.configurations.set(languageId, result); } return result; @@ -120,12 +120,12 @@ export class LanguageConfigurationService extends Disposable implements ILanguag function computeConfig( languageId: string, configurationService: IConfigurationService, - modeService: IModeService, + languageService: ILanguageService, ): ResolvedLanguageConfiguration { let languageConfig = LanguageConfigurationRegistry.getLanguageConfiguration(languageId); if (!languageConfig) { - const validLanguageId = modeService.validateLanguageId(languageId); + const validLanguageId = languageService.validateLanguageId(languageId); if (!validLanguageId) { throw new Error('Unexpected languageId'); } diff --git a/src/vs/editor/common/modes/modesRegistry.ts b/src/vs/editor/common/modes/modesRegistry.ts index 7eb2238c21277..cb5292f00ba46 100644 --- a/src/vs/editor/common/modes/modesRegistry.ts +++ b/src/vs/editor/common/modes/modesRegistry.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; +import { ILanguageExtensionPoint } from 'vs/editor/common/services/languageService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index 0af6b7f4bb4fe..d842a2c6b4231 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -7,11 +7,11 @@ import { Schemas } from 'vs/base/common/network'; import { DataUri, basenameOrAuthority } from 'vs/base/common/resources'; import { URI as uri } from 'vs/base/common/uri'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { FileKind } from 'vs/platform/files/common/files'; -export function getIconClasses(modelService: IModelService, modeService: IModeService, resource: uri | undefined, fileKind?: FileKind): string[] { +export function getIconClasses(modelService: IModelService, languageService: ILanguageService, resource: uri | undefined, fileKind?: FileKind): string[] { // we always set these base classes even if we do not have a path const classes = fileKind === FileKind.ROOT_FOLDER ? ['rootfolder-icon'] : fileKind === FileKind.FOLDER ? ['folder-icon'] : ['file-icon']; @@ -50,7 +50,7 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe } // Detected Mode - const detectedModeId = detectModeId(modelService, modeService, resource); + const detectedModeId = detectModeId(modelService, languageService, resource); if (detectedModeId) { classes.push(`${cssEscape(detectedModeId)}-lang-file-icon`); } @@ -64,7 +64,7 @@ export function getIconClassesForModeId(modeId: string): string[] { return ['file-icon', `${cssEscape(modeId)}-lang-file-icon`]; } -function detectModeId(modelService: IModelService, modeService: IModeService, resource: uri): string | null { +function detectModeId(modelService: IModelService, languageService: ILanguageService, resource: uri): string | null { if (!resource) { return null; // we need a resource at least } @@ -77,7 +77,7 @@ function detectModeId(modelService: IModelService, modeService: IModeService, re const mime = metadata.get(DataUri.META_DATA_MIME); if (mime) { - modeId = modeService.getModeId(mime); + modeId = languageService.getModeId(mime); } } @@ -95,7 +95,7 @@ function detectModeId(modelService: IModelService, modeService: IModeService, re } // otherwise fallback to path based detection - return modeService.getModeIdByFilepathOrFirstLine(resource); + return languageService.getModeIdByFilepathOrFirstLine(resource); } export function cssEscape(str: string): string { diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/languageService.ts similarity index 94% rename from src/vs/editor/common/services/modeService.ts rename to src/vs/editor/common/services/languageService.ts index 44049ddc32030..a1fedffc7a286 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/languageService.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { ILanguageIdCodec } from 'vs/editor/common/modes'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export const IModeService = createDecorator('modeService'); +export const ILanguageService = createDecorator('languageService'); export interface ILanguageExtensionPoint { id: string; @@ -26,7 +26,7 @@ export interface ILanguageSelection { readonly onDidChange: Event; } -export interface IModeService { +export interface ILanguageService { readonly _serviceBrand: undefined; readonly languageIdCodec: ILanguageIdCodec; diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 93bb212a89d8a..eb3297c614c89 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { ILanguageIdCodec, LanguageId } from 'vs/editor/common/modes'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { NULL_MODE_ID } from 'vs/editor/common/modes/nullMode'; -import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; +import { ILanguageExtensionPoint } from 'vs/editor/common/services/languageService'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index a044d8c942d81..a9298b20b1243 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -8,7 +8,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NULL_MODE_ID } from 'vs/editor/common/modes/nullMode'; import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; -import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageSelection, ILanguageService } from 'vs/editor/common/services/languageService'; import { firstOrDefault } from 'vs/base/common/arrays'; import { ILanguageIdCodec } from 'vs/editor/common/modes'; @@ -47,7 +47,7 @@ class LanguageSelection implements ILanguageSelection { } } -export class ModeServiceImpl extends Disposable implements IModeService { +export class ModeServiceImpl extends Disposable implements ILanguageService { public _serviceBrand: undefined; static instanceCount = 0; diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts index ab583a7390c65..7c55179d84614 100644 --- a/src/vs/editor/common/services/modelService.ts +++ b/src/vs/editor/common/services/modelService.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; 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 { ILanguageSelection } from 'vs/editor/common/services/languageService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider } from 'vs/editor/common/modes'; import { SemanticTokensProviderStyling } from 'vs/editor/common/services/semanticTokensProviderStyling'; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 7ba8ce0dce4d9..a21651b6d7d86 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -16,7 +16,7 @@ import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/modes'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageSelection, ILanguageService } from 'vs/editor/common/services/languageService'; 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'; @@ -162,7 +162,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { @IThemeService private readonly _themeService: IThemeService, @ILogService private readonly _logService: ILogService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService ) { super(); @@ -170,7 +170,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { this._models = {}; this._disposedModels = new Map(); this._disposedModelsHeapSize = 0; - this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._modeService, this._logService)); + this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._languageService, this._logService)); this._register(this._configurationService.onDidChangeConfiguration(() => this._updateModelOptions())); this._updateModelOptions(); @@ -374,7 +374,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { languageId, resource, this._undoRedoService, - this._modeService, + this._languageService, this._languageConfigurationService, ); if (resource && this._disposedModels.has(MODEL_ID(resource))) { @@ -704,7 +704,7 @@ class SemanticStyling extends Disposable { constructor( private readonly _themeService: IThemeService, - private readonly _modeService: IModeService, + private readonly _languageService: ILanguageService, private readonly _logService: ILogService ) { super(); @@ -716,7 +716,7 @@ class SemanticStyling extends Disposable { public get(provider: DocumentTokensProvider): SemanticTokensProviderStyling { if (!this._caches.has(provider)) { - this._caches.set(provider, new SemanticTokensProviderStyling(provider.getLegend(), this._themeService, this._modeService, this._logService)); + this._caches.set(provider, new SemanticTokensProviderStyling(provider.getLegend(), this._themeService, this._languageService, this._logService)); } return this._caches.get(provider)!; } diff --git a/src/vs/editor/common/services/semanticTokensProviderStyling.ts b/src/vs/editor/common/services/semanticTokensProviderStyling.ts index ec07461334ad6..186dd85c007c9 100644 --- a/src/vs/editor/common/services/semanticTokensProviderStyling.ts +++ b/src/vs/editor/common/services/semanticTokensProviderStyling.ts @@ -7,7 +7,7 @@ import { SemanticTokensLegend, TokenMetadata, FontStyle, MetadataConsts, Semanti 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'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; export const enum SemanticTokensProviderStylingConstants { NO_STYLING = 0b01111111111111111111111111111111 @@ -21,7 +21,7 @@ export class SemanticTokensProviderStyling { constructor( private readonly _legend: SemanticTokensLegend, @IThemeService private readonly _themeService: IThemeService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @ILogService private readonly _logService: ILogService ) { this._hashTable = new HashTable(); @@ -29,7 +29,7 @@ export class SemanticTokensProviderStyling { } public getMetadata(tokenTypeIndex: number, tokenModifierSet: number, languageId: string): number { - const encodedLanguageId = this._modeService.languageIdCodec.encodeLanguageId(languageId); + const encodedLanguageId = this._languageService.languageIdCodec.encodeLanguageId(languageId); const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet, encodedLanguageId); let metadata: number; if (entry) { diff --git a/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts b/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts index 2e7234034b145..b5fcd62bb9c18 100644 --- a/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts +++ b/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IPosition, Position } from 'vs/editor/common/core/position'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextResourceConfigurationService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService, ConfigurationTarget, IConfigurationValue, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; @@ -22,7 +22,7 @@ export class TextResourceConfigurationService extends Disposable implements ITex constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, ) { super(); this._register(this.configurationService.onDidChangeConfiguration(e => this._onDidChangeConfiguration.fire(this.toResourceConfigurationChangeEvent(e)))); @@ -111,7 +111,7 @@ export class TextResourceConfigurationService extends Disposable implements ITex if (model) { return position ? model.getLanguageIdAtPosition(position.lineNumber, position.column) : model.getLanguageId(); } - return this.modeService.getModeIdByFilepathOrFirstLine(resource); + return this.languageService.getModeIdByFilepathOrFirstLine(resource); } private toResourceConfigurationChangeEvent(configurationChangeEvent: IConfigurationChangeEvent): ITextResourceConfigurationChangeEvent { diff --git a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts index da3cf6560aba0..9928e2e20481e 100644 --- a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts @@ -12,7 +12,7 @@ import { ColorId, IState, MetadataConsts, TokenizationRegistry } from 'vs/editor import { CommentRule } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ILinePreflightData, IPreflightData, ISimpleModel, LineCommentCommand, Type } from 'vs/editor/contrib/comment/lineCommentCommand'; import { testCommand } from 'vs/editor/test/browser/testCommand'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -1082,7 +1082,7 @@ suite('Editor Contrib - Line Comment in mixed modes', () => { class OuterMode extends MockMode { constructor( commentsConfig: CommentRule, - @IModeService modeService: IModeService + @ILanguageService languageService: ILanguageService ) { super(OUTER_LANGUAGE_ID); this._register(LanguageConfigurationRegistry.register(this.languageId, { @@ -1096,7 +1096,7 @@ suite('Editor Contrib - Line Comment in mixed modes', () => { }, tokenize2: (line: string, hasEOL: boolean, state: IState): TokenizationResult2 => { const languageId = (/^ /.test(line) ? INNER_LANGUAGE_ID : OUTER_LANGUAGE_ID); - const encodedLanguageId = modeService.languageIdCodec.encodeLanguageId(languageId); + const encodedLanguageId = languageService.languageIdCodec.encodeLanguageId(languageId); const tokens = new Uint32Array(1 << 1); tokens[(0 << 1)] = 0; diff --git a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index bae5ecc623e5e..2b1b37fd19d1f 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -21,7 +21,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDeltaDecoration, ITextModel, IWordAtPosition } from 'vs/editor/common/model'; import { IFoundBracket } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairs'; import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; import { PeekContext } from 'vs/editor/contrib/peekView/peekView'; @@ -48,7 +48,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri constructor( editor: ICodeEditor, @ITextModelService private readonly textModelResolverService: ITextModelService, - @IModeService private readonly modeService: IModeService + @ILanguageService private readonly languageService: ILanguageService ) { this.editor = editor; @@ -204,7 +204,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri wordRange = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); } - const languageId = this.modeService.getModeIdByFilepathOrFirstLine(textEditorModel.uri); + const languageId = this.languageService.getModeIdByFilepathOrFirstLine(textEditorModel.uri); this.addDecoration( wordRange, new MarkdownString().appendCodeblock(languageId ? languageId : '', previewValue) diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 257e17562503a..108e10f0dd056 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -24,7 +24,7 @@ import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/ import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel'; import { Location } from 'vs/editor/common/modes'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { AccessibilityProvider, DataSource, Delegate, FileReferencesRenderer, IdentityProvider, OneReferenceRenderer, StringRepresentationProvider, TreeElement } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree'; import * as peekView from 'vs/editor/contrib/peekView/peekView'; @@ -223,7 +223,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { @ILabelService private readonly _uriLabel: ILabelService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, ) { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true, supportOnTitleClick: true }, _instantiationService); @@ -313,7 +313,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { }; this._preview = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._previewContainer, options, this.editor); dom.hide(this._previewContainer); - this._previewNotAvailableMessage = new TextModel(nls.localize('missingPreviewMessage', "no preview available"), TextModel.DEFAULT_CREATION_OPTIONS, null, null, this._undoRedoService, this._modeService, this._languageConfigurationService); + this._previewNotAvailableMessage = new TextModel(nls.localize('missingPreviewMessage', "no preview available"), TextModel.DEFAULT_CREATION_OPTIONS, null, null, this._undoRedoService, this._languageService, this._languageConfigurationService); // tree this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline')); diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 22b8064b34a07..86dfa41e327fa 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -12,7 +12,7 @@ import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution, IScrollEvent } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; import { ModesContentHoverWidget } from 'vs/editor/contrib/hover/modesContentHover'; @@ -50,7 +50,7 @@ export class ModesHoverController implements IEditorContribution { constructor(private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IOpenerService private readonly _openerService: IOpenerService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IContextKeyService _contextKeyService: IContextKeyService ) { this._isMouseDown = false; @@ -178,7 +178,7 @@ export class ModesHoverController implements IEditorContribution { if (targetType === MouseTargetType.GUTTER_GLYPH_MARGIN && mouseEvent.target.position) { this._contentWidget?.hide(); if (!this._glyphWidget) { - this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); + this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._languageService, this._openerService); } this._glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber); return; diff --git a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts index ee98defefa844..5ba8dd2cce92e 100644 --- a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts @@ -15,7 +15,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration } from 'vs/editor/common/model'; import { HoverProviderRegistry } from 'vs/editor/common/modes'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { getHover } from 'vs/editor/contrib/hover/getHover'; import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import * as nls from 'vs/nls'; @@ -47,7 +47,7 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant { hoverContentsElement.className = 'hover-contents code-hover-contents'; hover.onContentsChanged(); diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index 61a250a8259ca..b2ee5d6ab4ceb 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -10,7 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation'; import { Widget } from 'vs/base/browser/ui/widget'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; @@ -105,7 +105,7 @@ export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { constructor( editor: ICodeEditor, - modeService: IModeService, + languageService: ILanguageService, openerService: IOpenerService = NullOpenerService, ) { super(); @@ -118,7 +118,7 @@ export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { this._hover = this._register(new HoverWidget()); this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - this._markdownRenderer = this._register(new MarkdownRenderer({ editor: this._editor }, modeService, openerService)); + this._markdownRenderer = this._register(new MarkdownRenderer({ editor: this._editor }, languageService, openerService)); this._computer = new MarginComputer(this._editor); this._hoverOperation = new HoverOperation( this._computer, diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts index 11cc68ad6d18a..72d2e0086647e 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts @@ -16,7 +16,7 @@ import { Range } from 'vs/editor/common/core/range'; import { createStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { ILanguageIdCodec } from 'vs/editor/common/modes'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ghostTextBackground, ghostTextBorder, ghostTextForeground } from 'vs/editor/common/view/editorColorRegistry'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; @@ -30,14 +30,14 @@ const ttPolicy = window.trustedTypes?.createPolicy('editorGhostText', { createHT export class GhostTextWidget extends Disposable { private disposed = false; private readonly partsWidget = this._register(this.instantiationService.createInstance(DecorationsWidget, this.editor)); - private readonly additionalLinesWidget = this._register(new AdditionalLinesWidget(this.editor, this.modeService.languageIdCodec)); + private readonly additionalLinesWidget = this._register(new AdditionalLinesWidget(this.editor, this.languageService.languageIdCodec)); private viewMoreContentWidget: ViewMoreLinesContentWidget | undefined = undefined; constructor( private readonly editor: ICodeEditor, private readonly model: GhostTextWidgetModel, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, ) { super(); diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts index da760cf60d942..a095140cb768c 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts @@ -11,7 +11,7 @@ import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration } from 'vs/editor/common/model'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import { commitInlineSuggestionAction, GhostTextController, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction } from 'vs/editor/contrib/inlineCompletions/ghostTextController'; import * as nls from 'vs/nls'; @@ -48,7 +48,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan @ICommandService private readonly _commandService: ICommandService, @IMenuService private readonly _menuService: IMenuService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IOpenerService private readonly _openerService: IOpenerService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, ) { } @@ -147,7 +147,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan const $ = dom.$; const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderer = disposableStore.add(new MarkdownRenderer({ editor: this._editor }, this._modeService, this._openerService)); + const renderer = disposableStore.add(new MarkdownRenderer({ editor: this._editor }, this._languageService, this._openerService)); const render = (code: string) => { disposableStore.add(renderer.onDidRenderAsync(() => { hoverContentsElement.className = 'hover-contents code-hover-contents'; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 571f245fe46c8..444eacce13f96 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -17,7 +17,7 @@ import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/core/ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import * as modes from 'vs/editor/common/modes'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel'; import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp'; import * as nls from 'vs/nls'; @@ -61,10 +61,10 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { private readonly editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, ) { super(); - this.markdownRenderer = this._register(new MarkdownRenderer({ editor }, modeService, openerService)); + this.markdownRenderer = this._register(new MarkdownRenderer({ editor }, languageService, openerService)); this.model = this._register(new ParameterHintsModel(editor)); this.keyVisible = Context.Visible.bindTo(contextKeyService); this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService); diff --git a/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts b/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts index aca7b0cbcce99..68655b1697e87 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts @@ -18,7 +18,7 @@ import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/edit import { CompletionItemKind, CompletionItemTag, completionKindToCssClass } from 'vs/editor/common/modes'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import * as nls from 'vs/nls'; import { FileKind } from 'vs/platform/files/common/files'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; @@ -93,7 +93,7 @@ export class ItemRenderer implements IListRenderer detailClasses.length ? labelClasses : detailClasses; } else if (completion.kind === CompletionItemKind.Folder && this._themeService.getFileIconTheme().hasFolderIcons) { @@ -191,8 +191,8 @@ export class ItemRenderer implements IListRenderer { + public static colorizeElement(themeService: IStandaloneThemeService, languageService: ILanguageService, domNode: HTMLElement, options: IColorizerElementOptions): Promise { options = options || {}; let theme = options.theme || 'vs'; let mimeType = options.mimeType || domNode.getAttribute('lang') || domNode.getAttribute('data-lang'); @@ -45,11 +45,11 @@ export class Colorizer { const trustedhtml = ttPolicy?.createHTML(str) ?? str; domNode.innerHTML = trustedhtml as string; }; - return this.colorize(modeService, text || '', mimeType, options).then(render, (err) => console.error(err)); + return this.colorize(languageService, text || '', mimeType, options).then(render, (err) => console.error(err)); } - public static colorize(modeService: IModeService, text: string, mimeType: string, options: IColorizerOptions | null | undefined): Promise { - const languageIdCodec = modeService.languageIdCodec; + public static colorize(languageService: ILanguageService, text: string, mimeType: string, options: IColorizerOptions | null | undefined): Promise { + const languageIdCodec = languageService.languageIdCodec; let tabSize = 4; if (options && typeof options.tabSize === 'number') { tabSize = options.tabSize; @@ -59,13 +59,13 @@ export class Colorizer { text = text.substr(1); } let lines = strings.splitLines(text); - let language = modeService.getModeId(mimeType); + let language = languageService.getModeId(mimeType); if (!language) { return Promise.resolve(_fakeColorize(lines, tabSize, languageIdCodec)); } // Send out the event to create the mode - modeService.triggerMode(language); + languageService.triggerMode(language); const tokenizationSupport = TokenizationRegistry.get(language); if (tokenizationSupport) { diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index e588b8c45d536..044107118ce1f 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -17,7 +17,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { FontStyle, IState, ITokenizationSupport, StandardTokenType, TokenMetadata, TokenizationRegistry, ILanguageIdCodec } from 'vs/editor/common/modes'; import { NULL_STATE, nullTokenize, nullTokenize2 } from 'vs/editor/common/modes/nullMode'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -34,17 +34,17 @@ class InspectTokensController extends Disposable implements IEditorContribution } private readonly _editor: ICodeEditor; - private readonly _modeService: IModeService; + private readonly _languageService: ILanguageService; private _widget: InspectTokensWidget | null; constructor( editor: ICodeEditor, @IStandaloneThemeService standaloneColorService: IStandaloneThemeService, - @IModeService modeService: IModeService + @ILanguageService languageService: ILanguageService ) { super(); this._editor = editor; - this._modeService = modeService; + this._languageService = languageService; this._widget = null; this._register(this._editor.onDidChangeModel((e) => this.stop())); @@ -65,7 +65,7 @@ class InspectTokensController extends Disposable implements IEditorContribution if (!this._editor.hasModel()) { return; } - this._widget = new InspectTokensWidget(this._editor, this._modeService); + this._widget = new InspectTokensWidget(this._editor, this._languageService); } public stop(): void { @@ -151,22 +151,22 @@ class InspectTokensWidget extends Disposable implements IContentWidget { public allowEditorOverflow = true; private readonly _editor: IActiveCodeEditor; - private readonly _modeService: IModeService; + private readonly _languageService: ILanguageService; private readonly _tokenizationSupport: ITokenizationSupport; private readonly _model: ITextModel; private readonly _domNode: HTMLElement; constructor( editor: IActiveCodeEditor, - modeService: IModeService + languageService: ILanguageService ) { super(); this._editor = editor; - this._modeService = modeService; + this._languageService = languageService; this._model = this._editor.getModel(); this._domNode = document.createElement('div'); this._domNode.className = 'tokens-inspect-widget'; - this._tokenizationSupport = getSafeTokenizationSupport(this._modeService.languageIdCodec, this._model.getLanguageId()); + this._tokenizationSupport = getSafeTokenizationSupport(this._languageService.languageIdCodec, this._model.getLanguageId()); this._compute(this._editor.getPosition()); this._register(this._editor.onDidChangeCursorPosition((e) => this._compute(this._editor.getPosition()))); this._editor.addContentWidget(this); @@ -256,7 +256,7 @@ class InspectTokensWidget extends Disposable implements IContentWidget { let foreground = TokenMetadata.getForeground(metadata); let background = TokenMetadata.getBackground(metadata); return { - languageId: this._modeService.languageIdCodec.decodeLanguageId(languageId), + languageId: this._languageService.languageIdCodec.decodeLanguageId(languageId), tokenType: tokenType, fontStyle: fontStyle, foreground: colorMap[foreground], diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 726881de4050e..9ac0e9505a07b 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -32,7 +32,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageSelection, ILanguageService } from 'vs/editor/common/services/languageService'; import { URI } from 'vs/base/common/uri'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; import { Mimes } from 'vs/base/common/mime'; @@ -414,7 +414,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon @IConfigurationService configurationService: IConfigurationService, @IAccessibilityService accessibilityService: IAccessibilityService, @IModelService modelService: IModelService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, ) { const options = { ..._options }; updateConfigurationService(configurationService, options, false); @@ -437,7 +437,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon let model: ITextModel | null; if (typeof _model === 'undefined') { - model = createTextModel(modelService, modeService, options.value || '', options.language || Mimes.text, undefined); + model = createTextModel(modelService, languageService, options.value || '', options.language || Mimes.text, undefined); this._ownsModel = true; } else { model = _model; @@ -573,7 +573,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon /** * @internal */ -export function createTextModel(modelService: IModelService, modeService: IModeService, value: string, language: string | undefined, uri: URI | undefined): ITextModel { +export function createTextModel(modelService: IModelService, languageService: ILanguageService, value: string, language: string | undefined, uri: URI | undefined): ITextModel { value = value || ''; if (!language) { const firstLF = value.indexOf('\n'); @@ -581,9 +581,9 @@ export function createTextModel(modelService: IModelService, modeService: IModeS if (firstLF !== -1) { firstLine = value.substring(0, firstLF); } - return doCreateModel(modelService, value, modeService.createByFilepathOrFirstLine(uri || null, firstLine), uri); + return doCreateModel(modelService, value, languageService.createByFilepathOrFirstLine(uri || null, firstLine), uri); } - return doCreateModel(modelService, value, modeService.create(language), uri); + return doCreateModel(modelService, value, languageService.create(language), uri); } /** diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 438761bc36cdd..901fbd7e7b35a 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -18,7 +18,7 @@ import { FindMatch, ITextModel, TextModelResolvedOptions } from 'vs/editor/commo import * as modes from 'vs/editor/common/modes'; import { NULL_STATE, nullTokenize } from 'vs/editor/common/modes/nullMode'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from 'vs/editor/common/services/webWorker'; import * as standaloneEnums from 'vs/editor/common/standalone/standaloneEnums'; @@ -90,7 +90,7 @@ export function create(domElement: HTMLElement, options?: IStandaloneEditorConst services.get(IConfigurationService), services.get(IAccessibilityService), services.get(IModelService), - services.get(IModeService), + services.get(ILanguageService), ); }); } @@ -150,7 +150,7 @@ export function createDiffNavigator(diffEditor: IStandaloneDiffEditor, opts?: ID export function createModel(value: string, language?: string, uri?: URI): ITextModel { return createTextModel( StaticServices.modelService.get(), - StaticServices.modeService.get(), + StaticServices.languageService.get(), value, language, uri @@ -161,7 +161,7 @@ export function createModel(value: string, language?: string, uri?: URI): ITextM * Change the language for a model. */ export function setModelLanguage(model: ITextModel, languageId: string): void { - StaticServices.modelService.get().setMode(model, StaticServices.modeService.get().create(languageId)); + StaticServices.modelService.get().setMode(model, StaticServices.languageService.get().create(languageId)); } /** @@ -247,7 +247,7 @@ export function createWebWorker(opts: IWebWorkerOptions): MonacoWebWorker export function colorizeElement(domNode: HTMLElement, options: IColorizerElementOptions): Promise { const themeService = StaticServices.standaloneThemeService.get(); themeService.registerEditorContainer(domNode); - return Colorizer.colorizeElement(themeService, StaticServices.modeService.get(), domNode, options); + return Colorizer.colorizeElement(themeService, StaticServices.languageService.get(), domNode, options); } /** @@ -256,7 +256,7 @@ export function colorizeElement(domNode: HTMLElement, options: IColorizerElement export function colorize(text: string, languageId: string, options: IColorizerOptions): Promise { const themeService = StaticServices.standaloneThemeService.get(); themeService.registerEditorContainer(document.body); - return Colorizer.colorize(StaticServices.modeService.get(), text, languageId, options); + return Colorizer.colorize(StaticServices.languageService.get(), text, languageId, options); } /** @@ -286,9 +286,9 @@ function getSafeTokenizationSupport(language: string): Omit void): IDisposable { - let disposable = StaticServices.modeService.get().onDidEncounterLanguage((encounteredLanguageId) => { + let disposable = StaticServices.languageService.get().onDidEncounterLanguage((encounteredLanguageId) => { if (encounteredLanguageId === languageId) { // stop listening disposable.dispose(); @@ -64,7 +64,7 @@ export function onLanguage(languageId: string, callback: () => void): IDisposabl * Set the editing configuration for a language. */ export function setLanguageConfiguration(languageId: string, configuration: LanguageConfiguration): IDisposable { - const validLanguageId = StaticServices.modeService.get().validateLanguageId(languageId); + const validLanguageId = StaticServices.languageService.get().validateLanguageId(languageId); if (!validLanguageId) { throw new Error(`Cannot set configuration for unknown language ${languageId}`); } @@ -109,7 +109,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { constructor( private readonly _languageId: string, private readonly _actual: TokensProvider, - private readonly _modeService: IModeService, + private readonly _languageService: ILanguageService, private readonly _standaloneThemeService: IStandaloneThemeService, ) { } @@ -200,7 +200,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { public tokenize2(line: string, hasEOL: boolean, state: modes.IState, offsetDelta: number): TokenizationResult2 { let actualResult = this._actual.tokenize(line, state); - let tokens = this._toBinaryTokens(this._modeService.languageIdCodec, actualResult.tokens, offsetDelta); + let tokens = this._toBinaryTokens(this._languageService.languageIdCodec, actualResult.tokens, offsetDelta); let endState: modes.IState; // try to save an object if possible @@ -329,7 +329,7 @@ export function setColorMap(colorMap: string[] | null): void { * Set the tokens provider for a language (manual implementation). */ export function setTokensProvider(languageId: string, provider: TokensProvider | EncodedTokensProvider | Thenable): IDisposable { - const validLanguageId = StaticServices.modeService.get().validateLanguageId(languageId); + const validLanguageId = StaticServices.languageService.get().validateLanguageId(languageId); if (!validLanguageId) { throw new Error(`Cannot set tokens provider for unknown language ${languageId}`); } @@ -340,7 +340,7 @@ export function setTokensProvider(languageId: string, provider: TokensProvider | return new TokenizationSupport2Adapter( validLanguageId, provider, - StaticServices.modeService.get(), + StaticServices.languageService.get(), StaticServices.standaloneThemeService.get(), ); } @@ -357,7 +357,7 @@ export function setTokensProvider(languageId: string, provider: TokensProvider | */ export function setMonarchTokensProvider(languageId: string, languageDef: IMonarchLanguage | Thenable): IDisposable { const create = (languageDef: IMonarchLanguage) => { - return createTokenizationSupport(StaticServices.modeService.get(), StaticServices.standaloneThemeService.get(), languageId, compile(languageId, languageDef)); + return createTokenizationSupport(StaticServices.languageService.get(), StaticServices.standaloneThemeService.get(), languageId, compile(languageId, languageDef)); }; if (isThenable(languageDef)) { return modes.TokenizationRegistry.registerPromise(languageId, languageDef.then(languageDef => create(languageDef))); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index f36f435702ca1..d31081f80fceb 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -8,7 +8,7 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; @@ -150,7 +150,7 @@ export module StaticServices { export const markerService = define(IMarkerService, () => new MarkerService()); - export const modeService = define(IModeService, (o) => new ModeServiceImpl()); + export const languageService = define(ILanguageService, (o) => new ModeServiceImpl()); export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); @@ -158,7 +158,7 @@ export module StaticServices { export const undoRedoService = define(IUndoRedoService, (o) => new UndoRedoService(dialogService.get(o), notificationService.get(o))); - export const languageConfigurationService = define(ILanguageConfigurationService, (o) => new LanguageConfigurationService(configurationService.get(o), modeService.get(o))); + export const languageConfigurationService = define(ILanguageConfigurationService, (o) => new LanguageConfigurationService(configurationService.get(o), languageService.get(o))); export const modelService = define( IModelService, @@ -169,7 +169,7 @@ export module StaticServices { standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o), - modeService.get(o), + languageService.get(o), languageConfigurationService.get(o) ) ); diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index 24bf8c778176d..1ce3050ba9386 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -13,7 +13,7 @@ import { Token, TokenizationResult, TokenizationResult2 } from 'vs/editor/common import * as modes from 'vs/editor/common/modes'; import { NULL_MODE_ID, NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import * as monarchCommon from 'vs/editor/standalone/common/monarch/monarchCommon'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; @@ -287,15 +287,15 @@ class MonarchClassicTokensCollector implements IMonarchTokensCollector { class MonarchModernTokensCollector implements IMonarchTokensCollector { - private readonly _modeService: IModeService; + private readonly _languageService: ILanguageService; private readonly _theme: TokenTheme; private _prependTokens: Uint32Array | null; private _tokens: number[]; private _currentLanguageId: modes.LanguageId; private _lastTokenMetadata: number; - constructor(modeService: IModeService, theme: TokenTheme) { - this._modeService = modeService; + constructor(languageService: ILanguageService, theme: TokenTheme) { + this._languageService = languageService; this._theme = theme; this._prependTokens = null; this._tokens = []; @@ -304,7 +304,7 @@ class MonarchModernTokensCollector implements IMonarchTokensCollector { } public enterMode(startOffset: number, languageId: string): void { - this._currentLanguageId = this._modeService.languageIdCodec.encodeLanguageId(languageId); + this._currentLanguageId = this._languageService.languageIdCodec.encodeLanguageId(languageId); } public emit(startOffset: number, type: string): void { @@ -376,7 +376,7 @@ export type ILoadStatus = { loaded: true; } | { loaded: false; promise: Promise< export class MonarchTokenizer implements modes.ITokenizationSupport { - private readonly _modeService: IModeService; + private readonly _languageService: ILanguageService; private readonly _standaloneThemeService: IStandaloneThemeService; private readonly _languageId: string; private readonly _lexer: monarchCommon.ILexer; @@ -384,8 +384,8 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { public embeddedLoaded: Promise; private readonly _tokenizationRegistryListener: IDisposable; - constructor(modeService: IModeService, standaloneThemeService: IStandaloneThemeService, languageId: string, lexer: monarchCommon.ILexer) { - this._modeService = modeService; + constructor(languageService: ILanguageService, standaloneThemeService: IStandaloneThemeService, languageId: string, lexer: monarchCommon.ILexer) { + this._languageService = languageService; this._standaloneThemeService = standaloneThemeService; this._languageId = languageId; this._lexer = lexer; @@ -463,7 +463,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } public tokenize2(line: string, hasEOL: boolean, lineState: modes.IState, offsetDelta: number): TokenizationResult2 { - let tokensCollector = new MonarchModernTokensCollector(this._modeService, this._standaloneThemeService.getColorTheme().tokenTheme); + let tokensCollector = new MonarchModernTokensCollector(this._languageService, this._standaloneThemeService.getColorTheme().tokenTheme); let endLineState = this._tokenize(line, hasEOL, lineState, offsetDelta, tokensCollector); return tokensCollector.finalize(endLineState); } @@ -745,7 +745,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { const computeNewStateForEmbeddedMode = (enteringEmbeddedMode: string) => { // substitute language alias to known modes to support syntax highlighting - let enteringEmbeddedModeId = this._modeService.getModeIdForLanguageName(enteringEmbeddedMode); + let enteringEmbeddedModeId = this._languageService.getModeIdForLanguageName(enteringEmbeddedMode); if (enteringEmbeddedModeId) { enteringEmbeddedMode = enteringEmbeddedModeId; } @@ -859,7 +859,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } private _locateMode(mimetypeOrModeId: string): string | null { - if (!mimetypeOrModeId || !this._modeService.isRegisteredMode(mimetypeOrModeId)) { + if (!mimetypeOrModeId || !this._languageService.isRegisteredMode(mimetypeOrModeId)) { return null; } @@ -868,11 +868,11 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { return mimetypeOrModeId; } - const languageId = this._modeService.getModeId(mimetypeOrModeId); + const languageId = this._languageService.getModeId(mimetypeOrModeId); if (languageId) { // Fire mode loading event - this._modeService.triggerMode(languageId); + this._languageService.triggerMode(languageId); this._embeddedModes[languageId] = true; } @@ -902,6 +902,6 @@ function findBracket(lexer: monarchCommon.ILexer, matched: string) { return null; } -export function createTokenizationSupport(modeService: IModeService, standaloneThemeService: IStandaloneThemeService, languageId: string, lexer: monarchCommon.ILexer): modes.ITokenizationSupport { - return new MonarchTokenizer(modeService, standaloneThemeService, languageId, lexer); +export function createTokenizationSupport(languageService: ILanguageService, standaloneThemeService: IStandaloneThemeService, languageId: string, lexer: monarchCommon.ILexer): modes.ITokenizationSupport { + return new MonarchTokenizer(languageService, standaloneThemeService, languageId, lexer); } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index c202ab21ab5e1..8502a330bf762 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -113,12 +113,12 @@ suite('TokenizationSupport2Adapter', () => { } const disposables = new DisposableStore(); - const modeService = disposables.add(new ModeServiceImpl()); + const languageService = disposables.add(new ModeServiceImpl()); disposables.add(ModesRegistry.registerLanguage({ id: languageId })); const adapter = new TokenizationSupport2Adapter( languageId, new BadTokensProvider(), - modeService, + languageService, new MockThemeService() ); @@ -132,7 +132,7 @@ suite('TokenizationSupport2Adapter', () => { } // Add the encoded language id to the expected tokens - const encodedLanguageId = modeService.languageIdCodec.encodeLanguageId(languageId); + const encodedLanguageId = languageService.languageIdCodec.encodeLanguageId(languageId); const tokenLanguageMetadata = (encodedLanguageId << MetadataConsts.LANGUAGEID_OFFSET); for (let i = 1; i < expectedModernTokens.length; i += 2) { expectedModernTokens[i] |= tokenLanguageMetadata; diff --git a/src/vs/editor/standalone/test/monarch/monarch.test.ts b/src/vs/editor/standalone/test/monarch/monarch.test.ts index 0bc2c5044458f..e0eacc902188e 100644 --- a/src/vs/editor/standalone/test/monarch/monarch.test.ts +++ b/src/vs/editor/standalone/test/monarch/monarch.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { MonarchTokenizer } from 'vs/editor/standalone/common/monarch/monarchLexer'; import { compile } from 'vs/editor/standalone/common/monarch/monarchCompile'; import { Token } from 'vs/editor/common/core/token'; @@ -15,8 +15,8 @@ import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; suite('Monarch', () => { - function createMonarchTokenizer(modeService: IModeService, languageId: string, language: IMonarchLanguage): MonarchTokenizer { - return new MonarchTokenizer(modeService, null!, languageId, compile(languageId, language)); + function createMonarchTokenizer(languageService: ILanguageService, languageId: string, language: IMonarchLanguage): MonarchTokenizer { + return new MonarchTokenizer(languageService, null!, languageId, compile(languageId, language)); } function getTokens(tokenizer: MonarchTokenizer, lines: string[]): Token[][] { @@ -31,11 +31,11 @@ suite('Monarch', () => { } test('Ensure @rematch and nextEmbedded can be used together in Monarch grammar', () => { - const modeService = new ModeServiceImpl(); + const languageService = new ModeServiceImpl(); const innerModeRegistration = ModesRegistry.registerLanguage({ id: 'sql' }); - const innerModeTokenizationRegistration = TokenizationRegistry.register('sql', createMonarchTokenizer(modeService, 'sql', { + const innerModeTokenizationRegistration = TokenizationRegistry.register('sql', createMonarchTokenizer(languageService, 'sql', { tokenizer: { root: [ [/./, 'token'] @@ -43,7 +43,7 @@ suite('Monarch', () => { } })); const SQL_QUERY_START = '(SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|WITH)'; - const tokenizer = createMonarchTokenizer(modeService, 'test1', { + const tokenizer = createMonarchTokenizer(languageService, 'test1', { tokenizer: { root: [ [`(\"\"\")${SQL_QUERY_START}`, [{ 'token': 'string.quote', }, { token: '@rematch', next: '@endStringWithSQL', nextEmbedded: 'sql', },]], @@ -106,12 +106,12 @@ suite('Monarch', () => { ]); innerModeTokenizationRegistration.dispose(); innerModeRegistration.dispose(); - modeService.dispose(); + languageService.dispose(); }); test('microsoft/monaco-editor#1235: Empty Line Handling', () => { - const modeService = new ModeServiceImpl(); - const tokenizer = createMonarchTokenizer(modeService, 'test', { + const languageService = new ModeServiceImpl(); + const tokenizer = createMonarchTokenizer(languageService, 'test', { tokenizer: { root: [ { include: '@comments' }, @@ -162,13 +162,13 @@ suite('Monarch', () => { [], [new Token(0, 'source.test', 'test')] ]); - modeService.dispose(); + languageService.dispose(); }); test('microsoft/monaco-editor#2265: Exit a state at end of line', () => { - const modeService = new ModeServiceImpl(); - const tokenizer = createMonarchTokenizer(modeService, 'test', { + const languageService = new ModeServiceImpl(); + const tokenizer = createMonarchTokenizer(languageService, 'test', { includeLF: true, tokenizer: { root: [ @@ -211,13 +211,13 @@ suite('Monarch', () => { new Token(18, 'number.test', 'test'), ] ]); - modeService.dispose(); + languageService.dispose(); }); test('issue #115662: monarchCompile function need an extra option which can control replacement', () => { - const modeService = new ModeServiceImpl(); + const languageService = new ModeServiceImpl(); - const tokenizer1 = createMonarchTokenizer(modeService, 'test', { + const tokenizer1 = createMonarchTokenizer(languageService, 'test', { ignoreCase: false, uselessReplaceKey1: '@uselessReplaceKey2', uselessReplaceKey2: '@uselessReplaceKey3', @@ -236,7 +236,7 @@ suite('Monarch', () => { }, }); - const tokenizer2 = createMonarchTokenizer(modeService, 'test', { + const tokenizer2 = createMonarchTokenizer(languageService, 'test', { ignoreCase: false, tokenizer: { root: [ @@ -265,13 +265,13 @@ suite('Monarch', () => { new Token(0, 'ham.test', 'test'), ] ]); - modeService.dispose(); + languageService.dispose(); }); test('microsoft/monaco-editor#2424: Allow to target @@', () => { - const modeService = new ModeServiceImpl(); + const languageService = new ModeServiceImpl(); - const tokenizer = createMonarchTokenizer(modeService, 'test', { + const tokenizer = createMonarchTokenizer(languageService, 'test', { ignoreCase: false, tokenizer: { root: [ @@ -293,7 +293,7 @@ suite('Monarch', () => { new Token(0, 'ham.test', 'test'), ] ]); - modeService.dispose(); + languageService.dispose(); }); }); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index d98fa99b351b0..384de76c2e933 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -24,7 +24,7 @@ import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { DisposableStore } from 'vs/base/common/lifecycle'; // --------- utils @@ -4771,7 +4771,7 @@ suite('autoClosingPairs', () => { private static readonly _id = 'autoClosingMode'; - constructor(modeService: IModeService | null = null) { + constructor(languageService: ILanguageService | null = null) { super(AutoClosingMode._id); this._register(LanguageConfigurationRegistry.register(this.languageId, { autoClosingPairs: [ @@ -4823,8 +4823,8 @@ suite('autoClosingPairs', () => { } type State = BaseState | StringState | BlockCommentState; - if (modeService) { - const encodedLanguageId = modeService.languageIdCodec.encodeLanguageId(this.languageId); + if (languageService) { + const encodedLanguageId = languageService.languageIdCodec.encodeLanguageId(this.languageId); this._register(TokenizationRegistry.register(this.languageId, { getInitialState: () => new BaseState(), tokenize: undefined!, @@ -4977,8 +4977,8 @@ suite('autoClosingPairs', () => { test('issue #132912: quotes should not auto-close if they are closing a string', () => { const disposables = new DisposableStore(); const instantiationService = createCodeEditorServices(disposables); - const modeService = instantiationService.invokeFunction((accessor) => accessor.get(IModeService)); - const mode = disposables.add(new AutoClosingMode(modeService)); + const languageService = instantiationService.invokeFunction((accessor) => accessor.get(ILanguageService)); + const mode = disposables.add(new AutoClosingMode(languageService)); withTestCodeEditor( null, { diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 699df653c4db0..4a6bfe28ca0f3 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -15,7 +15,7 @@ import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; @@ -182,7 +182,7 @@ export function createCodeEditorServices(disposables: DisposableStore, services: define(INotificationService, TestNotificationService); define(IDialogService, TestDialogService); define(IUndoRedoService, UndoRedoService); - define(IModeService, ModeServiceImpl); + define(ILanguageService, ModeServiceImpl); define(ILanguageConfigurationService, TestLanguageConfigurationService); define(IConfigurationService, TestConfigurationService); define(ITextResourcePropertiesService, TestTextResourcePropertiesService); diff --git a/src/vs/editor/test/common/editorTestUtils.ts b/src/vs/editor/test/common/editorTestUtils.ts index ed7ddc5ad3d7d..8695311077ded 100644 --- a/src/vs/editor/test/common/editorTestUtils.ts +++ b/src/vs/editor/test/common/editorTestUtils.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { BracketPairColorizationOptions, DefaultEndOfLine, ITextBufferFactory, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; @@ -94,7 +94,7 @@ export function createModelServices(disposables: DisposableStore, services: Serv define(INotificationService, TestNotificationService); define(IDialogService, TestDialogService); define(IUndoRedoService, UndoRedoService); - define(IModeService, ModeServiceImpl); + define(ILanguageService, ModeServiceImpl); define(ILanguageConfigurationService, TestLanguageConfigurationService); define(IConfigurationService, TestConfigurationService); define(ITextResourcePropertiesService, TestTextResourcePropertiesService); diff --git a/src/vs/editor/test/common/mocks/mockMode.ts b/src/vs/editor/test/common/mocks/mockMode.ts index 8c85d77634729..b3cb1fff1b9be 100644 --- a/src/vs/editor/test/common/mocks/mockMode.ts +++ b/src/vs/editor/test/common/mocks/mockMode.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { ILanguageSelection } from 'vs/editor/common/services/modeService'; +import { ILanguageSelection } from 'vs/editor/common/services/languageService'; export class MockMode extends Disposable { constructor( diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts index 6c2f478464a2a..a55f3c46bf790 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts @@ -14,7 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { IState, ITokenizationSupport, LanguageId, MetadataConsts, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/modes'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { createModelServices, createTextModel2 } from 'vs/editor/test/common/editorTestUtils'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; @@ -23,9 +23,9 @@ suite('Bracket Pair Colorizer - Tokenizer', () => { const mode1 = 'testMode1'; const disposableStore = new DisposableStore(); const instantiationService = createModelServices(disposableStore); - const modeService = instantiationService.invokeFunction((accessor) => accessor.get(IModeService)); + const languageService = instantiationService.invokeFunction((accessor) => accessor.get(ILanguageService)); disposableStore.add(ModesRegistry.registerLanguage({ id: mode1 })); - const encodedMode1 = modeService.languageIdCodec.encodeLanguageId(mode1); + const encodedMode1 = languageService.languageIdCodec.encodeLanguageId(mode1); const denseKeyProvider = new DenseKeyProvider(); diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 4389feb930e51..9fd29817b38b9 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -16,7 +16,7 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { createModelServices, createTextModel, createTextModel2 } from 'vs/editor/test/common/editorTestUtils'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; // --------- utils @@ -384,10 +384,10 @@ suite('Editor Model - Words', () => { class OuterMode extends MockMode { constructor( - @IModeService modeService: IModeService + @ILanguageService languageService: ILanguageService ) { super(OUTER_LANGUAGE_ID); - const languageIdCodec = modeService.languageIdCodec; + const languageIdCodec = languageService.languageIdCodec; this._register(LanguageConfigurationRegistry.register(this.languageId, {})); diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 43712a942f77f..4598ec7572bc3 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -15,7 +15,7 @@ import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ViewLineToken } from 'vs/editor/test/common/core/viewLineToken'; import { createModelServices, createTextModel, createTextModel2 } from 'vs/editor/test/common/editorTestUtils'; @@ -350,7 +350,7 @@ suite('TextModelWithTokens', () => { const mode1 = 'testMode1'; const mode2 = 'testMode2'; - const languageIdCodec = instantiationService.invokeFunction((accessor) => accessor.get(IModeService).languageIdCodec); + const languageIdCodec = instantiationService.invokeFunction((accessor) => accessor.get(ILanguageService).languageIdCodec); disposables.add(ModesRegistry.registerLanguage({ id: mode1 })); disposables.add(ModesRegistry.registerLanguage({ id: mode2 })); @@ -453,7 +453,7 @@ suite('TextModelWithTokens', () => { const instantiationService = createModelServices(disposables); const mode = 'testMode'; - const languageIdCodec = instantiationService.invokeFunction((accessor) => accessor.get(IModeService).languageIdCodec); + const languageIdCodec = instantiationService.invokeFunction((accessor) => accessor.get(ILanguageService).languageIdCodec); const encodedMode = languageIdCodec!.encodeLanguageId(mode); @@ -673,7 +673,7 @@ suite('TextModelWithTokens regression tests', () => { disposables.add(ModesRegistry.registerLanguage({ id: outerMode })); disposables.add(ModesRegistry.registerLanguage({ id: innerMode })); - const languageIdCodec = instantiationService.invokeFunction((accessor) => accessor.get(IModeService).languageIdCodec); + const languageIdCodec = instantiationService.invokeFunction((accessor) => accessor.get(ILanguageService).languageIdCodec); const encodedInnerMode = languageIdCodec.encodeLanguageId(innerMode); const tokenizationSupport: ITokenizationSupport = { diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index e0e12f527041b..6f4d10db8b278 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -29,7 +29,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { getDocumentSemanticTokens, isSemanticTokens } from 'vs/editor/common/services/getSemanticTokens'; @@ -411,7 +411,7 @@ suite('ModelSemanticColoring', () => { const disposables = new DisposableStore(); const ORIGINAL_FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY = ModelSemanticColoring.FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY; let modelService: IModelService; - let modeService: IModeService; + let languageService: ILanguageService; setup(() => { ModelSemanticColoring.FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY = 0; @@ -428,7 +428,7 @@ suite('ModelSemanticColoring', () => { disposables.add(new ModeServiceImpl()), new TestLanguageConfigurationService() )); - modeService = disposables.add(new ModeServiceImpl(false)); + languageService = disposables.add(new ModeServiceImpl(false)); }); teardown(() => { @@ -469,7 +469,7 @@ suite('ModelSemanticColoring', () => { } })); - const textModel = disposables.add(modelService.createModel('Hello world', modeService.create('testMode'))); + const textModel = disposables.add(modelService.createModel('Hello world', languageService.create('testMode'))); // wait for the provider to be called await inFirstCall.wait(); @@ -532,7 +532,7 @@ suite('ModelSemanticColoring', () => { return result; } - const textModel = modelService.createModel('Hello world 2', modeService.create('testMode2')); + const textModel = modelService.createModel('Hello world 2', languageService.create('testMode2')); try { let result = await getDocumentSemanticTokens(textModel, null, null, CancellationToken.None); assert.ok(result, `We should have tokens (1)`); diff --git a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts index dff36f24346bc..91f82ab489780 100644 --- a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts +++ b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IConfigurationValue, IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { TextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationServiceImpl'; import { URI } from 'vs/base/common/uri'; @@ -32,7 +32,7 @@ suite('TextResourceConfigurationService - Update', () => { setup(() => { const instantiationService = new TestInstantiationService(); instantiationService.stub(IModelService, >{ getModel() { return null; } }); - instantiationService.stub(IModeService, >{ getModeIdByFilepathOrFirstLine() { return language; } }); + instantiationService.stub(ILanguageService, >{ getModeIdByFilepathOrFirstLine() { return language; } }); instantiationService.stub(IConfigurationService, configurationService); testObject = instantiationService.createInstance(TextResourceConfigurationService); }); diff --git a/src/vs/workbench/api/browser/mainThreadDocumentContentProviders.ts b/src/vs/workbench/api/browser/mainThreadDocumentContentProviders.ts index c8fb41198bfa2..70e2777f43356 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentContentProviders.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentContentProviders.ts @@ -11,7 +11,7 @@ import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, ExtHostDocumentContentProvidersShape, IExtHostContext, MainContext, MainThreadDocumentContentProvidersShape } from '../common/extHost.protocol'; @@ -27,7 +27,7 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon constructor( extHostContext: IExtHostContext, @ITextModelService private readonly _textModelResolverService: ITextModelService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService ) { @@ -45,7 +45,7 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon return this._proxy.$provideTextDocumentContent(handle, uri).then(value => { if (typeof value === 'string') { const firstLineText = value.substr(0, 1 + value.search(/\r?\n/)); - const languageSelection = this._modeService.createByFilepathOrFirstLine(uri, firstLineText); + const languageSelection = this._languageService.createByFilepathOrFirstLine(uri, firstLineText); return this._modelService.createModel(value, languageSelection, uri); } return null; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 3f14d2df3ef91..bd230ce2136d6 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -14,7 +14,7 @@ import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; @@ -28,17 +28,17 @@ import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticToken export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { private readonly _proxy: ExtHostLanguageFeaturesShape; - private readonly _modeService: IModeService; + private readonly _languageService: ILanguageService; private readonly _registrations = new Map(); constructor( extHostContext: IExtHostContext, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures); - this._modeService = modeService; + this._languageService = languageService; - if (this._modeService) { + if (this._languageService) { const updateAllWordDefinitions = () => { const langWordPairs = LanguageConfigurationRegistry.getWordDefinitions(); let wordDefinitionDtos: ILanguageWordDefinitionDto[] = []; @@ -770,7 +770,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; } - const validLanguageId = this._modeService.validateLanguageId(languageId); + const validLanguageId = this._languageService.validateLanguageId(languageId); if (validLanguageId) { this._registrations.set(handle, LanguageConfigurationRegistry.register(validLanguageId, configuration, 100)); } diff --git a/src/vs/workbench/api/browser/mainThreadLanguages.ts b/src/vs/workbench/api/browser/mainThreadLanguages.ts index 86a49e0698a28..2a8825b0d7cbc 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguages.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguages.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI, UriComponents } from 'vs/base/common/uri'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { MainThreadLanguagesShape, MainContext, IExtHostContext, ExtHostContext, ExtHostLanguagesShape } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -25,16 +25,16 @@ export class MainThreadLanguages implements MainThreadLanguagesShape { constructor( _extHostContext: IExtHostContext, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService, @ITextModelService private _resolverService: ITextModelService, @ILanguageStatusService private readonly _languageStatusService: ILanguageStatusService, ) { this._proxy = _extHostContext.getProxy(ExtHostContext.ExtHostLanguages); - this._proxy.$acceptLanguageIds(_modeService.getRegisteredModes()); - this._disposables.add(_modeService.onLanguagesMaybeChanged(e => { - this._proxy.$acceptLanguageIds(_modeService.getRegisteredModes()); + this._proxy.$acceptLanguageIds(_languageService.getRegisteredModes()); + this._disposables.add(_languageService.onLanguagesMaybeChanged(e => { + this._proxy.$acceptLanguageIds(_languageService.getRegisteredModes()); })); } @@ -49,7 +49,7 @@ export class MainThreadLanguages implements MainThreadLanguagesShape { async $changeLanguage(resource: UriComponents, languageId: string): Promise { - const validLanguageId = this._modeService.validateLanguageId(languageId); + const validLanguageId = this._languageService.validateLanguageId(languageId); if (!validLanguageId || validLanguageId !== languageId) { return Promise.reject(new Error(`Unknown language id: ${languageId}`)); } @@ -57,7 +57,7 @@ export class MainThreadLanguages implements MainThreadLanguagesShape { const uri = URI.revive(resource); const ref = await this._resolverService.createModelReference(uri); try { - this._modelService.setMode(ref.object.textEditorModel, this._modeService.create(languageId)); + this._modelService.setMode(ref.object.textEditorModel, this._languageService.create(languageId)); } finally { ref.dispose(); } diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index f870f23ef0723..973c95f155148 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -8,7 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -46,7 +46,7 @@ abstract class MainThreadKernel implements INotebookKernel { return flatten(this.preloads.map(p => p.provides)); } - constructor(data: INotebookKernelDto2, private _modeService: IModeService) { + constructor(data: INotebookKernelDto2, private _languageService: ILanguageService) { this.id = data.id; this.viewType = data.notebookType; this.extension = data.extensionId; @@ -56,7 +56,7 @@ abstract class MainThreadKernel implements INotebookKernel { this.description = data.description; this.detail = data.detail; this.kind = data.kind; - this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _modeService.getRegisteredModes(); + this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _languageService.getRegisteredModes(); this.implementsExecutionOrder = data.supportsExecutionOrder ?? false; this.localResourceRoot = URI.revive(data.extensionLocation); this.preloads = data.preloads?.map(u => ({ uri: URI.revive(u.uri), provides: u.provides })) ?? []; @@ -83,7 +83,7 @@ abstract class MainThreadKernel implements INotebookKernel { event.kind = true; } if (data.supportedLanguages !== undefined) { - this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : this._modeService.getRegisteredModes(); + this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : this._languageService.getRegisteredModes(); event.supportedLanguages = true; } if (data.supportsExecutionOrder !== undefined) { @@ -110,7 +110,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape constructor( extHostContext: IExtHostContext, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, @INotebookExecutionService private readonly _notebookExecutionService: INotebookExecutionService, // @INotebookService private readonly _notebookService: INotebookService, @@ -197,7 +197,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape async cancelNotebookCellExecution(uri: URI, handles: number[]): Promise { await that._proxy.$cancelCells(handle, uri, handles); } - }(data, this._modeService); + }(data, this._languageService); const listener = this._notebookKernelService.onDidChangeSelectedNotebooks(e => { if (e.oldKernel === kernel.id) { diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index eaba787ac2e50..8768c552f843e 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -17,7 +17,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { ILabelService } from 'vs/platform/label/common/label'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IRecent, isRecentFolder, isRecentWorkspace, IWorkspacesService, IWorkspaceIdentifier, isFolderBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; @@ -75,7 +75,7 @@ abstract class BaseOpenRecentAction extends Action2 { const labelService = accessor.get(ILabelService); const keybindingService = accessor.get(IKeybindingService); const modelService = accessor.get(IModelService); - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const hostService = accessor.get(IHostService); const dialogService = accessor.get(IDialogService); @@ -113,19 +113,19 @@ abstract class BaseOpenRecentAction extends Action2 { for (const recent of recentlyOpened.workspaces) { const isDirty = isRecentFolder(recent) ? dirtyFolders.has(recent.folderUri) : dirtyWorkspaces.has(recent.workspace.configPath); - workspacePicks.push(this.toQuickPick(modelService, modeService, labelService, recent, isDirty)); + workspacePicks.push(this.toQuickPick(modelService, languageService, labelService, recent, isDirty)); } // Fill any backup workspace that is not yet shown at the end for (const dirtyWorkspaceOrFolder of dirtyWorkspacesAndFolders) { if (isFolderBackupInfo(dirtyWorkspaceOrFolder) && !recentFolders.has(dirtyWorkspaceOrFolder.folderUri)) { - workspacePicks.push(this.toQuickPick(modelService, modeService, labelService, dirtyWorkspaceOrFolder, true)); + workspacePicks.push(this.toQuickPick(modelService, languageService, labelService, dirtyWorkspaceOrFolder, true)); } else if (isWorkspaceBackupInfo(dirtyWorkspaceOrFolder) && !recentWorkspaces.has(dirtyWorkspaceOrFolder.workspace.configPath)) { - workspacePicks.push(this.toQuickPick(modelService, modeService, labelService, dirtyWorkspaceOrFolder, true)); + workspacePicks.push(this.toQuickPick(modelService, languageService, labelService, dirtyWorkspaceOrFolder, true)); } } - const filePicks = recentlyOpened.files.map(p => this.toQuickPick(modelService, modeService, labelService, p, false)); + const filePicks = recentlyOpened.files.map(p => this.toQuickPick(modelService, languageService, labelService, p, false)); // focus second entry if the first recent workspace is the current workspace const firstEntry = recentlyOpened.workspaces[0]; @@ -182,7 +182,7 @@ abstract class BaseOpenRecentAction extends Action2 { } } - private toQuickPick(modelService: IModelService, modeService: IModeService, labelService: ILabelService, recent: IRecent, isDirty: boolean): IRecentlyOpenedPick { + private toQuickPick(modelService: IModelService, languageService: ILanguageService, labelService: ILabelService, recent: IRecent, isDirty: boolean): IRecentlyOpenedPick { let openable: IWindowOpenable | undefined; let iconClasses: string[]; let fullLabel: string | undefined; @@ -192,7 +192,7 @@ abstract class BaseOpenRecentAction extends Action2 { // Folder if (isRecentFolder(recent)) { resource = recent.folderUri; - iconClasses = getIconClasses(modelService, modeService, resource, FileKind.FOLDER); + iconClasses = getIconClasses(modelService, languageService, resource, FileKind.FOLDER); openable = { folderUri: resource }; fullLabel = recent.label || labelService.getWorkspaceLabel(resource, { verbose: true }); } @@ -200,7 +200,7 @@ abstract class BaseOpenRecentAction extends Action2 { // Workspace else if (isRecentWorkspace(recent)) { resource = recent.workspace.configPath; - iconClasses = getIconClasses(modelService, modeService, resource, FileKind.ROOT_FOLDER); + iconClasses = getIconClasses(modelService, languageService, resource, FileKind.ROOT_FOLDER); openable = { workspaceUri: resource }; fullLabel = recent.label || labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); isWorkspace = true; @@ -209,7 +209,7 @@ abstract class BaseOpenRecentAction extends Action2 { // File else { resource = recent.fileUri; - iconClasses = getIconClasses(modelService, modeService, resource, FileKind.FILE); + iconClasses = getIconClasses(modelService, languageService, resource, FileKind.FILE); openable = { fileUri: resource }; fullLabel = recent.label || labelService.getUriLabel(resource); } diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index 660acfaf05cc2..fa9524f23c253 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -16,7 +16,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IFileDialogService, IPickAndOpenOptions } from 'vs/platform/dialogs/common/dialogs'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; @@ -86,7 +86,7 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, async functio const labelService = accessor.get(ILabelService); const contextService = accessor.get(IWorkspaceContextService); const modelService = accessor.get(IModelService); - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const folders = contextService.getWorkspace().folders; if (!folders.length) { @@ -98,7 +98,7 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, async functio label: folder.name, description: labelService.getUriLabel(dirname(folder.uri), { relative: true }), folder, - iconClasses: getIconClasses(modelService, modeService, folder.uri, FileKind.ROOT_FOLDER) + iconClasses: getIconClasses(modelService, languageService, folder.uri, FileKind.ROOT_FOLDER) }; }); diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 4f06051a0fe57..ecebc408f3a42 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources'; import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -114,7 +114,7 @@ export class ResourceLabels extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IDecorationsService private readonly decorationsService: IDecorationsService, @IThemeService private readonly themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @@ -133,7 +133,7 @@ export class ResourceLabels extends Disposable { })); // notify when extensions are registered with potentially new languages - this._register(this.modeService.onLanguagesMaybeChanged(() => this.widgets.forEach(widget => widget.notifyExtensionsRegistered()))); + this._register(this.languageService.onLanguagesMaybeChanged(() => this.widgets.forEach(widget => widget.notifyExtensionsRegistered()))); // notify when model mode changes this._register(this.modelService.onModelModeChanged(e => { @@ -250,13 +250,13 @@ export class ResourceLabel extends ResourceLabels { @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, @IModelService modelService: IModelService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IDecorationsService decorationsService: IDecorationsService, @IThemeService themeService: IThemeService, @ILabelService labelService: ILabelService, @ITextFileService textFileService: ITextFileService ) { - super(DEFAULT_LABELS_CONTAINER, instantiationService, configurationService, modelService, modeService, decorationsService, themeService, labelService, textFileService); + super(DEFAULT_LABELS_CONTAINER, instantiationService, configurationService, modelService, languageService, decorationsService, themeService, labelService, textFileService); this.label = this._register(this.create(container, options)); } @@ -285,7 +285,7 @@ class ResourceLabelWidget extends IconLabel { constructor( container: HTMLElement, options: IIconLabelCreationOptions | undefined, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IModelService private readonly modelService: IModelService, @IDecorationsService private readonly decorationsService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, @@ -535,7 +535,7 @@ class ResourceLabelWidget extends IconLabel { if (this.options && !this.options.hideIcon) { if (!this.computedIconClasses) { - this.computedIconClasses = getIconClasses(this.modelService, this.modeService, resource, this.options.fileKind); + this.computedIconClasses = getIconClasses(this.modelService, this.languageService, resource, this.options.fileKind); } iconLabelOptions.extraClasses = this.computedIconClasses.slice(0); diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 896407ca8bd3c..349330d31a6b9 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -11,7 +11,7 @@ import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/ import { EditorsOrder, IEditorIdentifier, EditorResourceAccessor, SideBySideEditor, GroupIdentifier } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { prepareQuery, scoreItemFuzzy, compareItemsByFuzzyScore, FuzzyScorerCache } from 'vs/base/common/fuzzyScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -46,7 +46,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, @IEditorService protected readonly editorService: IEditorService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService + @ILanguageService private readonly languageService: ILanguageService ) { super(prefix, { @@ -156,7 +156,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro return isDirty ? localize('entryAriaLabelDirty', "{0}, unsaved changes", nameAndDescription) : nameAndDescription; })(), description, - iconClasses: getIconClasses(this.modelService, this.modeService, resource).concat(editor.getLabelExtraClasses()), + iconClasses: getIconClasses(this.modelService, this.languageService, resource).concat(editor.getLabelExtraClasses()), italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), buttons: (() => { return [ @@ -197,9 +197,9 @@ export class ActiveGroupEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQ @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IModelService modelService: IModelService, - @IModeService modeService: IModeService + @ILanguageService languageService: ILanguageService ) { - super(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + super(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, languageService); } protected doGetEditors(): IEditorIdentifier[] { @@ -222,9 +222,9 @@ export class AllEditorsByAppearanceQuickAccess extends BaseEditorQuickAccessProv @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IModelService modelService: IModelService, - @IModeService modeService: IModeService + @ILanguageService languageService: ILanguageService ) { - super(AllEditorsByAppearanceQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + super(AllEditorsByAppearanceQuickAccess.PREFIX, editorGroupService, editorService, modelService, languageService); } protected doGetEditors(): IEditorIdentifier[] { @@ -253,9 +253,9 @@ export class AllEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAcce @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IModelService modelService: IModelService, - @IModeService modeService: IModeService + @ILanguageService languageService: ILanguageService ) { - super(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + super(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, languageService); } protected doGetEditors(): IEditorIdentifier[] { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index f079c6ce595db..b7f6dc59e89fd 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -25,7 +25,7 @@ import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/bina import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; +import { ILanguageService, ILanguageSelection } from 'vs/editor/common/services/languageService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; @@ -318,7 +318,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { constructor( @IEditorService private readonly editorService: IEditorService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @ITextFileService private readonly textFileService: ITextFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @INotificationService private readonly notificationService: INotificationService, @@ -739,7 +739,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const textModel = editorWidget.getModel(); if (textModel) { const languageId = textModel.getLanguageId(); - info.mode = withNullAsUndefined(this.modeService.getLanguageName(languageId)); + info.mode = withNullAsUndefined(this.languageService.getLanguageName(languageId)); } } @@ -1080,7 +1080,7 @@ export class ChangeModeAction extends Action { constructor( actionId: string, actionLabel: string, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -1108,7 +1108,7 @@ export class ChangeModeAction extends Action { let currentModeId: string | undefined; if (textModel) { currentModeId = textModel.getLanguageId(); - currentLanguageId = withNullAsUndefined(this.modeService.getLanguageName(currentModeId)); + currentLanguageId = withNullAsUndefined(this.languageService.getLanguageName(currentModeId)); } let hasLanguageSupport = !!resource; @@ -1117,11 +1117,11 @@ export class ChangeModeAction extends Action { } // All languages are valid picks - const languages = this.modeService.getRegisteredLanguageNames(); + const languages = this.languageService.getRegisteredLanguageNames(); const picks: QuickPickInput[] = languages.sort() .map(lang => { - const languageId = this.modeService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown'; - const extensions = this.modeService.getExtensions(lang).join(' '); + const languageId = this.languageService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown'; + const extensions = this.languageService.getExtensions(lang).join(' '); let description: string; if (currentLanguageId === lang) { description = localize('languageDescription', "({0}) - Configured Language", languageId); @@ -1201,23 +1201,23 @@ export class ChangeModeAction extends Action { const resource = EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); if (resource) { // Detect languages since we are in an untitled file - let languageId: string | undefined = withNullAsUndefined(this.modeService.getModeIdByFilepathOrFirstLine(resource, textModel.getLineContent(1))); + let languageId: string | undefined = withNullAsUndefined(this.languageService.getModeIdByFilepathOrFirstLine(resource, textModel.getLineContent(1))); if (!languageId) { detectedLanguage = await this.languageDetectionService.detectLanguage(resource); languageId = detectedLanguage; } if (languageId) { - languageSelection = this.modeService.create(languageId); + languageSelection = this.languageService.create(languageId); } } } } else { - languageSelection = this.modeService.createByLanguageName(pick.label); + languageSelection = this.languageService.createByLanguageName(pick.label); if (resource) { // fire and forget to not slow things down this.languageDetectionService.detectLanguage(resource).then(detectedModeId => { - const chosenModeId = this.modeService.getModeIdForLanguageName(pick.label.toLowerCase()) || 'unknown'; + const chosenModeId = this.languageService.getModeIdForLanguageName(pick.label.toLowerCase()) || 'unknown'; if (detectedModeId === currentModeId && currentModeId !== chosenModeId) { // If they didn't choose the detected language (which should also be the active language if automatic detection is enabled) // then the automatic language detection was likely wrong and the user is correcting it. In this case, we want telemetry. @@ -1247,11 +1247,11 @@ export class ChangeModeAction extends Action { private configureFileAssociation(resource: URI): void { const extension = extname(resource); const base = basename(resource); - const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(URI.file(base)); + const currentAssociation = this.languageService.getModeIdByFilepathOrFirstLine(URI.file(base)); - const languages = this.modeService.getRegisteredLanguageNames(); + const languages = this.languageService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { - const id = withNullAsUndefined(this.modeService.getModeIdForLanguageName(lang.toLowerCase())) || 'unknown'; + const id = withNullAsUndefined(this.languageService.getModeIdForLanguageName(lang.toLowerCase())) || 'unknown'; return { id, diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 80f49e9e3f0fd..7ce3a573889d3 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -23,7 +23,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { EditorOption, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ModelConstants } from 'vs/editor/common/model'; @@ -145,7 +145,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService + @ILanguageService private readonly languageService: ILanguageService ) { super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); } @@ -198,12 +198,12 @@ export class TextResourceEditor extends AbstractTextResourceEditor { // We can still try to guess a good mode from the first line if // the paste changed the first line else { - candidateMode = withNullAsUndefined(this.modeService.getModeIdByFilepathOrFirstLine(textModel.uri, textModel.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT))); + candidateMode = withNullAsUndefined(this.languageService.getModeIdByFilepathOrFirstLine(textModel.uri, textModel.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT))); } // Finally apply mode to model if specified if (candidateMode !== PLAINTEXT_MODE_ID) { - this.modelService.setMode(textModel, this.modeService.create(candidateMode)); + this.modelService.setMode(textModel, this.languageService.create(candidateMode)); } } } diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index 698c46fa2c38b..2855654dec317 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -8,7 +8,7 @@ import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { IModeSupport } from 'vs/workbench/services/textfile/common/textfiles'; import { URI } from 'vs/base/common/uri'; import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; -import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; +import { ILanguageService, ILanguageSelection } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { MutableDisposable } from 'vs/base/common/lifecycle'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; @@ -34,7 +34,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel constructor( @IModelService protected modelService: IModelService, - @IModeService protected modeService: IModeService, + @ILanguageService protected languageService: ILanguageService, @ILanguageDetectionService private readonly languageDetectionService: ILanguageDetectionService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, textEditorModelHandle?: URI @@ -95,7 +95,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel return; } - this.modelService.setMode(this.textEditorModel, this.modeService.create(mode)); + this.modelService.setMode(this.textEditorModel, this.languageService.create(mode)); } getMode(): string | undefined { @@ -118,7 +118,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel const lang = await this.languageDetectionService.detectLanguage(this.textEditorModelHandle); if (lang && !this.isDisposed()) { this.setModeInternal(lang); - const languageName = this.modeService.getLanguageName(lang); + const languageName = this.languageService.getLanguageName(lang); if (languageName) { this.accessibilityService.alert(localize('languageAutoDetected', "Language {0} was automatically detected and set as the language mode.", languageName)); } @@ -131,7 +131,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel */ protected createTextEditorModel(value: ITextBufferFactory, resource: URI | undefined, preferredMode?: string): ITextModel { const firstLineText = this.getFirstLineText(value); - const languageSelection = this.getOrCreateMode(resource, this.modeService, preferredMode, firstLineText); + const languageSelection = this.getOrCreateMode(resource, this.languageService, preferredMode, firstLineText); return this.doCreateTextEditorModel(value, languageSelection, resource); } @@ -171,15 +171,15 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel * * @param firstLineText optional first line of the text buffer to set the mode on. This can be used to guess a mode from content. */ - protected getOrCreateMode(resource: URI | undefined, modeService: IModeService, preferredMode: string | undefined, firstLineText?: string): ILanguageSelection { + protected getOrCreateMode(resource: URI | undefined, languageService: ILanguageService, preferredMode: string | undefined, firstLineText?: string): ILanguageSelection { // lookup mode via resource path if the provided mode is unspecific if (!preferredMode || preferredMode === PLAINTEXT_MODE_ID) { - return modeService.createByFilepathOrFirstLine(withUndefinedAsNull(resource), firstLineText); + return languageService.createByFilepathOrFirstLine(withUndefinedAsNull(resource), firstLineText); } // otherwise take the preferred mode for granted - return modeService.create(preferredMode); + return languageService.create(preferredMode); } /** @@ -197,7 +197,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel // mode (only if specific and changed) if (preferredMode && preferredMode !== PLAINTEXT_MODE_ID && this.textEditorModel.getLanguageId() !== preferredMode) { - this.modelService.setMode(this.textEditorModel, this.modeService.create(preferredMode)); + this.modelService.setMode(this.textEditorModel, this.languageService.create(preferredMode)); } } diff --git a/src/vs/workbench/common/editor/textResourceEditorModel.ts b/src/vs/workbench/common/editor/textResourceEditorModel.ts index 33917ef092b11..a417a53965f82 100644 --- a/src/vs/workbench/common/editor/textResourceEditorModel.ts +++ b/src/vs/workbench/common/editor/textResourceEditorModel.ts @@ -5,7 +5,7 @@ import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { URI } from 'vs/base/common/uri'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -18,12 +18,12 @@ export class TextResourceEditorModel extends BaseTextEditorModel { constructor( resource: URI, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IModelService modelService: IModelService, @ILanguageDetectionService languageDetectionService: ILanguageDetectionService, @IAccessibilityService accessibilityService: IAccessibilityService, ) { - super(modelService, modeService, languageDetectionService, accessibilityService, resource); + super(modelService, languageService, languageDetectionService, accessibilityService, resource); } override dispose(): void { diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index b5eea2b652d77..806b429388634 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -9,7 +9,7 @@ import { deepClone, equals } from 'vs/base/common/objects'; import { Emitter } from 'vs/base/common/event'; import { basename, dirname, extname, relativePath, isEqual } from 'vs/base/common/resources'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IFileService } from 'vs/platform/files/common/files'; import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; @@ -48,7 +48,7 @@ export class ResourceContextKey implements IContextKey { constructor( @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IFileService private readonly _fileService: IFileService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService, ) { this._schemeKey = ResourceContextKey.Scheme.bindTo(this._contextKeyService); @@ -66,7 +66,7 @@ export class ResourceContextKey implements IContextKey { this._isFileSystemResource.set(Boolean(resource && _fileService.hasProvider(resource))); })); - this._disposables.add(_modeService.onDidEncounterLanguage(this._setLangId, this)); + this._disposables.add(_languageService.onDidEncounterLanguage(this._setLangId, this)); this._disposables.add(_modelService.onModelAdded(model => { if (isEqual(model.uri, this.get())) { this._setLangId(); @@ -89,7 +89,7 @@ export class ResourceContextKey implements IContextKey { this._langIdKey.set(null); return; } - const langId = this._modelService.getModel(value)?.getLanguageId() ?? this._modeService.getModeIdByFilepathOrFirstLine(value); + const langId = this._modelService.getModel(value)?.getLanguageId() ?? this._languageService.getModeIdByFilepathOrFirstLine(value); this._langIdKey.set(langId); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index b60166bc74373..aa908f8da870c 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -5,7 +5,7 @@ import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { WorkspaceEditMetadata } from 'vs/editor/common/modes'; @@ -366,7 +366,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { constructor( private readonly _operations: BulkFileOperations, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService, @ITextModelService private readonly _textModelResolverService: ITextModelService ) { @@ -416,7 +416,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { const sourceModel = ref.object.textEditorModel; model = this._modelService.createModel( createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), - this._modeService.create(sourceModel.getLanguageId()), + this._languageService.create(sourceModel.getLanguageId()), previewUri ); ref.dispose(); @@ -425,7 +425,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { // create NEW model model = this._modelService.createModel( '', - this._modeService.createByFilepathOrFirstLine(previewUri), + this._languageService.createByFilepathOrFirstLine(previewUri), previewUri ); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index 6a0adba6911d0..d092aea71010b 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -28,7 +28,7 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { Iterable } from 'vs/base/common/iterator'; import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; // --- VIEW MODEL @@ -180,7 +180,7 @@ export class BulkEditDataSource implements IAsyncDataSource(); // Listen for hints that a language configuration is needed/usefull and then load it once - this._modeService.onDidEncounterLanguage((languageIdentifier) => { + this._languageService.onDidEncounterLanguage((languageIdentifier) => { // Modes can be instantiated before the extension points have finished registering this._extensionService.whenInstalledExtensionsRegistered().then(() => { this._loadConfigurationsForMode(languageIdentifier); @@ -106,7 +106,7 @@ export class LanguageConfigurationFileHandler { } this._done.add(languageId); - const configurationFiles = this._modeService.getConfigurationFiles(languageId); + const configurationFiles = this._languageService.getConfigurationFiles(languageId); configurationFiles.forEach((configFileLocation) => this._handleConfigFile(languageId, configFileLocation)); } diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 24e1f33d7c1e3..163fd7af48666 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -12,7 +12,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -80,7 +80,7 @@ export class CommentNode extends Disposable { @IInstantiationService private instantiationService: IInstantiationService, @ICommentService private commentService: ICommentService, @IModelService private modelService: IModelService, - @IModeService private modeService: IModeService, + @ILanguageService private languageService: ILanguageService, @INotificationService private notificationService: INotificationService, @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService @@ -334,7 +334,7 @@ export class CommentNode extends Disposable { const container = dom.append(editContainer, dom.$('.edit-textarea')); this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(), this.parentEditor, this.parentThread); const resource = URI.parse(`comment:commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`); - this._commentEditorModel = this.modelService.createModel('', this.modeService.createByFilepathOrFirstLine(resource), resource, false); + this._commentEditorModel = this.modelService.createModel('', this.languageService.createByFilepathOrFirstLine(resource), resource, false); this._commentEditor.setModel(this._commentEditorModel); this._commentEditor.setValue(this.comment.body.value); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 64a84a2e282a5..5cba5cb4827a6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -20,7 +20,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { peekViewBorder } from 'vs/editor/contrib/peekView/peekView'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; @@ -161,7 +161,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _commentThread: modes.CommentThread, private _pendingComment: string | null, @IInstantiationService private instantiationService: IInstantiationService, - @IModeService private modeService: IModeService, + @ILanguageService private languageService: ILanguageService, @IModelService private modelService: IModelService, @IThemeService private themeService: IThemeService, @ICommentService private commentService: ICommentService, @@ -205,7 +205,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget })); this._applyTheme(this.themeService.getColorTheme()); - this._markdownRenderer = this._globalToDispose.add(new MarkdownRenderer({ editor }, this.modeService, this.openerService)); + this._markdownRenderer = this._globalToDispose.add(new MarkdownRenderer({ editor }, this.languageService, this.openerService)); this._parentEditor = editor; } @@ -562,7 +562,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget resource = resource.with({ authority: commentController.id }); } - const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource), resource, false); + const model = this.modelService.createModel(this._pendingComment || '', this.languageService.createByFilepathOrFirstLine(resource), resource, false); this._disposables.add(model); commentEditor.setModel(model); this._disposables.add(commentEditor); diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 2bc4947ea77a0..750e56cf3bb51 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -11,7 +11,7 @@ import * as strings from 'vs/base/common/strings'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorModel } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -53,7 +53,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { @ICommandService private readonly commandService: ICommandService, @IExtensionService private readonly extensionService: IExtensionService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IDialogService private readonly dialogService: IDialogService, @ILifecycleService private readonly lifecycleService: ILifecycleService, ) { @@ -308,7 +308,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { model = activeTextEditorControl.getModel(); const language = model ? model.getLanguageId() : undefined; if (language) { - languageLabel = this.modeService.getLanguageName(language); + languageLabel = this.languageService.getLanguageName(language); } const adapters = this.debuggers .filter(a => a.enabled) diff --git a/src/vs/workbench/contrib/debug/common/debugContentProvider.ts b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts index 4cacca17688a7..68e56e8a36a33 100644 --- a/src/vs/workbench/contrib/debug/common/debugContentProvider.ts +++ b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { guessMimeTypes, Mimes } from 'vs/base/common/mime'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { DEBUG_SCHEME, IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; @@ -41,7 +41,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC @ITextModelService textModelResolverService: ITextModelService, @IDebugService private readonly debugService: IDebugService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService ) { textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this); @@ -94,7 +94,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC } const createErrModel = (errMsg?: string) => { this.debugService.sourceIsNotAvailable(resource); - const languageSelection = this.modeService.create(Mimes.text); + const languageSelection = this.languageService.create(Mimes.text); const message = errMsg ? localize('canNotResolveSourceWithError', "Could not load source '{0}': {1}.", resource.path, errMsg) : localize('canNotResolveSource', "Could not load source '{0}'.", resource.path); @@ -134,7 +134,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC } else { // create text model const mime = response.body.mimeType || guessMimeTypes(resource)[0]; - const languageSelection = this.modeService.create(mime); + const languageSelection = this.languageService.create(mime); return this.modelService.createModel(response.body.content, languageSelection, resource); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 918fe5f0e6d7f..f4c8cf006efc5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -57,7 +57,7 @@ import { platform } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; import { buttonForeground, buttonHoverBackground, editorBackground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -211,7 +211,7 @@ export class ExtensionEditor extends EditorPane { @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IWebviewService private readonly webviewService: IWebviewService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { @@ -786,7 +786,7 @@ export class ExtensionEditor extends EditorPane { private async renderMarkdown(cacheResult: CacheResult, container: HTMLElement) { const contents = await this.loadContents(() => cacheResult, container); - const content = await renderMarkdownDocument(contents, this.extensionService, this.modeService); + const content = await renderMarkdownDocument(contents, this.extensionService, this.languageService); return this.renderBody(content); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index db58fb870c72b..412e9a93142c6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -34,7 +34,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension } from 'vs/platform/extensions/common/extensions'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IProductService } from 'vs/platform/product/common/productService'; import { FileAccess } from 'vs/base/common/network'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; @@ -637,7 +637,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IProgressService private readonly progressService: IProgressService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IStorageService private readonly storageService: IStorageService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService, @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, @IProductService private readonly productService: IProductService, @@ -805,8 +805,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const keywords = lookup[ext] || []; // Get mode name - const languageId = this.modeService.getModeIdByFilepathOrFirstLine(URI.file(`.${ext}`)); - const languageName = languageId && this.modeService.getLanguageName(languageId); + const languageId = this.languageService.getModeIdByFilepathOrFirstLine(URI.file(`.${ext}`)); + const languageName = languageId && this.languageService.getLanguageName(languageId); const languageTag = languageName ? ` tag:"${languageName}"` : ''; // Construct a rich query diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index 8fd50b5d57370..dda72eaabf198 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -23,7 +23,7 @@ import { URI } from 'vs/base/common/uri'; import { Mimes, guessMimeTypes } from 'vs/base/common/mime'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { distinct } from 'vs/base/common/arrays'; @@ -95,7 +95,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { @IExtensionService private readonly extensionService: IExtensionService, @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IProductService productService: IProductService, @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -197,7 +197,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { private async promptRecommendations(uri: URI, language: string, fileExtension: string): Promise { const importantRecommendations: string[] = (this.fileBasedRecommendationsByLanguage.get(language) || []).filter(extensionId => this.importantExtensionTips.has(extensionId)); - let languageName: string | null = importantRecommendations.length ? this.modeService.getLanguageName(language) : null; + let languageName: string | null = importantRecommendations.length ? this.languageService.getLanguageName(language) : null; const fileBasedRecommendations: string[] = [...importantRecommendations]; for (let [pattern, extensionIds] of this.fileBasedRecommendationsByPattern) { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 47c6eb7b4931a..d011448648082 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -23,7 +23,7 @@ import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_IN_GROUP_COMMAND_ID, NEW_UNTITL import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -753,13 +753,13 @@ export class CompareWithClipboardAction extends Action { class ClipboardContentProvider implements ITextModelContentProvider { constructor( @IClipboardService private readonly clipboardService: IClipboardService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IModelService private readonly modelService: IModelService ) { } async provideTextContent(resource: URI): Promise { const text = await this.clipboardService.readText(); - const model = this.modelService.createModel(text, this.modeService.createByFilepathOrFirstLine(resource), resource); + const model = this.modelService.createModel(text, this.languageService.createByFilepathOrFirstLine(resource), resource); return model; } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 50343f5eeb286..69a2b1885771e 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -13,7 +13,7 @@ import { ITextModelContentProvider } from 'vs/editor/common/services/resolverSer import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; +import { ILanguageService, ILanguageSelection } from 'vs/editor/common/services/languageService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -131,7 +131,7 @@ export class TextFileContentProvider extends Disposable implements ITextModelCon constructor( @ITextFileService private readonly textFileService: ITextFileService, @IFileService private readonly fileService: IFileService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IModelService private readonly modelService: IModelService ) { super(); @@ -198,9 +198,9 @@ export class TextFileContentProvider extends Disposable implements ITextModelCon let languageSelector: ILanguageSelection; if (textFileModel) { - languageSelector = this.modeService.create(textFileModel.getLanguageId()); + languageSelector = this.languageService.create(textFileModel.getLanguageId()); } else { - languageSelector = this.modeService.createByFilepathOrFirstLine(savedFileResource); + languageSelector = this.languageService.createByFilepathOrFirstLine(savedFileResource); } codeEditorModel = this.modelService.createModel(content.value, languageSelector, resource); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index 37b4d3f0dc1c7..d61bc455b67dc 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -25,7 +25,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextModel } from 'vs/editor/common/model'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -47,7 +47,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { @INotificationService private readonly _notificationService: INotificationService, @IDialogService private readonly _dialogService: IDialogService, @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, ) { super(); this._register(this._extensionService.onDidChangeExtensions(this._updateConfigValues, this)); @@ -111,7 +111,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { const extension = await this._extensionService.getExtension(defaultFormatterId); if (extension && this._extensionEnablementService.isEnabled(toExtension(extension))) { // formatter does not target this file - const langName = this._modeService.getLanguageName(document.getLanguageId()) || document.getLanguageId(); + const langName = this._languageService.getLanguageName(document.getLanguageId()) || document.getLanguageId(); const detail = nls.localize('miss', "Extension '{0}' is configured as formatter but it cannot format '{1}'-files", extension.displayName || extension.name, langName); if (mode === FormattingMode.Silent) { this._notificationService.status(detail, { hideAfter: 4000 }); @@ -135,7 +135,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { return formatter[0]; } - const langName = this._modeService.getLanguageName(document.getLanguageId()) || document.getLanguageId(); + const langName = this._languageService.getLanguageName(document.getLanguageId()) || document.getLanguageId(); const message = !defaultFormatterId ? nls.localize('config.needed', "There are multiple formatters for '{0}' files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName)) : nls.localize('config.bad', "Extension '{0}' is configured as formatter but not available. Select a different default formatter to continue.", defaultFormatterId); @@ -172,7 +172,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { description: formatter.extensionId && formatter.extensionId.value }; }); - const langName = this._modeService.getLanguageName(document.getLanguageId()) || document.getLanguageId(); + const langName = this._languageService.getLanguageName(document.getLanguageId()) || document.getLanguageId(); const pick = await this._quickInputService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for '{0}' files", DefaultFormatter._maybeQuotes(langName)) }); if (!pick || !formatter[pick.index].extensionId) { return undefined; @@ -231,7 +231,7 @@ function logFormatterTelemetry( async function showFormatterPick(accessor: ServicesAccessor, model: ITextModel, formatters: FormattingEditProvider[]): Promise { const quickPickService = accessor.get(IQuickInputService); const configService = accessor.get(IConfigurationService); - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const overrides = { resource: model.uri, overrideIdentifier: model.getLanguageId() }; const defaultFormatter = configService.getValue(DefaultFormatter.configName, overrides); @@ -270,7 +270,7 @@ async function showFormatterPick(accessor: ServicesAccessor, model: ITextModel, } else if (pick === configurePick) { // config default - const langName = modeService.getLanguageName(model.getLanguageId()) || model.getLanguageId(); + const langName = languageService.getLanguageName(model.getLanguageId()) || model.getLanguageId(); const pick = await quickPickService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for '{0}' files", DefaultFormatter._maybeQuotes(langName)) }); if (pick && formatters[pick.index].extensionId) { configService.updateValue(DefaultFormatter.configName, formatters[pick.index].extensionId!.value, overrides); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index e5ec850f34fde..d9b7c2db8d33b 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -30,7 +30,7 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe import { ExecutionStateCellStatusBarContrib, TimerCellStatusBarContrib } from 'vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; @@ -80,7 +80,7 @@ export class InteractiveEditor extends EditorPane { // #inputLineCount = 1; #notebookWidgetService: INotebookEditorService; #instantiationService: IInstantiationService; - #modeService: IModeService; + #languageService: ILanguageService; #contextKeyService: IContextKeyService; #notebookKernelService: INotebookKernelService; #keybindingService: IKeybindingService; @@ -103,7 +103,7 @@ export class InteractiveEditor extends EditorPane { @IContextKeyService contextKeyService: IContextKeyService, @ICodeEditorService codeEditorService: ICodeEditorService, @INotebookKernelService notebookKernelService: INotebookKernelService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IKeybindingService keybindingService: IKeybindingService, @IInteractiveHistoryService historyService: IInteractiveHistoryService, @IConfigurationService configurationService: IConfigurationService, @@ -120,7 +120,7 @@ export class InteractiveEditor extends EditorPane { this.#notebookWidgetService = notebookWidgetService; this.#contextKeyService = contextKeyService; this.#notebookKernelService = notebookKernelService; - this.#modeService = modeService; + this.#languageService = languageService; this.#keybindingService = keybindingService; this.#historyService = historyService; this.#menuService = menuService; @@ -513,7 +513,7 @@ export class InteractiveEditor extends EditorPane { if (selectedOrSuggested) { const language = selectedOrSuggested.supportedLanguages[0]; - const newMode = language ? this.#modeService.create(language).languageId : PLAINTEXT_MODE_ID; + const newMode = language ? this.#languageService.create(language).languageId : PLAINTEXT_MODE_ID; textModel.setMode(newMode); } } diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 3a5b9c4856549..00249a4652368 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -8,7 +8,7 @@ import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; import { ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/modes'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export const DEFAULT_MARKDOWN_STYLES = ` @@ -199,7 +199,7 @@ function sanitize(documentContent: string, allowUnknownProtocols: boolean): stri export async function renderMarkdownDocument( text: string, extensionService: IExtensionService, - modeService: IModeService, + languageService: ILanguageService, shouldSanitize: boolean = true, allowUnknownProtocols: boolean = false, ): Promise { @@ -210,12 +210,12 @@ export async function renderMarkdownDocument( } extensionService.whenInstalledExtensionsRegistered().then(async () => { let support: ITokenizationSupport | undefined; - const languageId = modeService.getModeIdForLanguageName(lang); + const languageId = languageService.getModeIdForLanguageName(lang); if (languageId) { - modeService.triggerMode(languageId); + languageService.triggerMode(languageId); support = await TokenizationRegistry.getPromise(languageId) ?? undefined; } - callback(null, `${tokenizeToString(code, modeService.languageIdCodec, support)}`); + callback(null, `${tokenizeToString(code, languageService.languageIdCodec, support)}`); }); return ''; }; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts index 8d7db31e8e862..6b131a0da5c9f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts @@ -6,7 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -23,7 +23,7 @@ class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemP constructor( @INotebookService private readonly _notebookService: INotebookService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, ) { } async provideCellStatusBarItems(uri: URI, index: number, _token: CancellationToken): Promise { @@ -35,8 +35,8 @@ class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemP const languageId = cell.cellKind === CellKind.Markup ? 'markdown' : - (this._modeService.getModeIdForLanguageName(cell.language) || cell.language); - const text = this._modeService.getLanguageName(languageId) || languageId; + (this._languageService.getModeIdForLanguageName(cell.language) || cell.language); + const text = this._languageService.getLanguageName(languageId) || languageId; const item = { text, command: CHANGE_CELL_LANGUAGE, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts b/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts index 1fa546d1e024f..0edd0d9c4e660 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts @@ -7,7 +7,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RenderOutputType, ICellOutputViewModel, IRenderOutput } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { OutputRendererRegistry } from 'vs/workbench/contrib/notebook/browser/view/output/rendererRegistry'; @@ -29,7 +29,7 @@ abstract class CodeRendererContrib extends Disposable implements IOutputTransfor public notebookEditor: INotebookDelegateForOutput, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, ) { super(); } @@ -60,7 +60,7 @@ abstract class CodeRendererContrib extends Disposable implements IOutputTransfor container.style.height = `${editorHeight + 8}px`; })); - const mode = this.modeService.create(languageId); + const mode = this.languageService.create(languageId); const textModel = this.modelService.createModel(value, mode, undefined, false); editor.setModel(textModel); @@ -81,7 +81,7 @@ abstract class CodeRendererContrib extends Disposable implements IOutputTransfor export class NotebookCodeRendererContribution extends Disposable { - constructor(@IModeService _modeService: IModeService) { + constructor(@ILanguageService _languageService: ILanguageService) { super(); const registeredMimeTypes = new Map(); @@ -104,11 +104,11 @@ export class NotebookCodeRendererContribution extends Disposable { registeredMimeTypes.set(mimeType, true); }; - _modeService.getRegisteredModes().forEach(id => { + _languageService.getRegisteredModes().forEach(id => { registerCodeRendererContrib(`text/x-${id}`, id); }); - this._register(_modeService.onDidEncounterLanguage((languageId) => { + this._register(_languageService.onDidEncounterLanguage((languageId) => { registerCodeRendererContrib(`text/x-${languageId}`, languageId); })); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts index 6aac1a92f6b45..048679da70847 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { ITextBuffer, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { IConfigurationService, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -73,7 +73,7 @@ suite('Notebook Find', () => { editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 3, count: 0, cells: [ - new TestCell(viewModel.viewType, 3, '# next paragraph 1', 'markdown', CellKind.Code, [], accessor.get(IModeService)), + new TestCell(viewModel.viewType, 3, '# next paragraph 1', 'markdown', CellKind.Code, [], accessor.get(ILanguageService)), ] }], true, undefined, () => undefined, undefined, true); assert.strictEqual(editor.textModel.length, 4); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/test/notebookUndoRedo.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/test/notebookUndoRedo.test.ts index 92e7552757708..a7c9d01f063e0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/test/notebookUndoRedo.test.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/test/notebookUndoRedo.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { CellEditType, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { TestCell, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -16,7 +16,7 @@ suite('Notebook Undo/Redo', () => { ['body', 'markdown', CellKind.Markup, [], {}], ], async (editor, viewModel, accessor) => { - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); assert.strictEqual(viewModel.length, 2); assert.strictEqual(viewModel.getVersionId(), 0); assert.strictEqual(viewModel.getAlternativeId(), '0_0,1;1,1'); @@ -40,7 +40,7 @@ suite('Notebook Undo/Redo', () => { editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 0, cells: [ - new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], modeService), + new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], languageService), ] }], true, undefined, () => undefined, undefined, true); assert.strictEqual(viewModel.getVersionId(), 4); @@ -60,7 +60,7 @@ suite('Notebook Undo/Redo', () => { ['body', 'markdown', CellKind.Markup, [], {}], ], async (editor, viewModel, accessor) => { - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 2, cells: [] }], true, undefined, () => undefined, undefined, true); @@ -68,7 +68,7 @@ suite('Notebook Undo/Redo', () => { assert.doesNotThrow(() => { editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 2, cells: [ - new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], modeService), + new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], languageService), ] }], true, undefined, () => undefined, undefined, true); }); @@ -101,14 +101,14 @@ suite('Notebook Undo/Redo', () => { ['body', 'markdown', CellKind.Markup, [], {}], ], async (editor, viewModel, accessor) => { - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 2, cells: [] }], true, undefined, () => undefined, undefined, true); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 2, cells: [ - new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], modeService), + new TestCell(viewModel.viewType, 3, '# header 2', 'markdown', CellKind.Code, [], languageService), ] }], true, undefined, () => undefined, undefined, true); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts index d7e77dc70e9d9..6545eeb70ce07 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts @@ -7,7 +7,7 @@ import { IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/brow import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference, IReadonlyTextBuffer } from 'vs/editor/common/model'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { INotebookActionContext, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState, CellFocusMode, expandCellRangesWithHiddenCells, IActiveNotebookEditor, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -574,7 +574,7 @@ export function computeCellLinesContents(cell: ICellViewModel, splitPoints: IPos } export function insertCell( - modeService: IModeService, + languageService: ILanguageService, editor: IActiveNotebookEditor, index: number, type: CellKind, @@ -592,7 +592,7 @@ export function insertCell( const nextIndex = ui ? viewModel.getNextVisibleCellIndex(index) : index + 1; let language; if (type === CellKind.Code) { - const supportedLanguages = activeKernel?.supportedLanguages ?? modeService.getRegisteredModes(); + const supportedLanguages = activeKernel?.supportedLanguages ?? languageService.getRegisteredModes(); const defaultLanguage = supportedLanguages[0] || 'plaintext'; if (cell?.cellKind === CellKind.Code) { language = cell.language; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 662a0027eef01..2bc48672377a6 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { localize } from 'vs/nls'; import { MenuId, MenuItemAction, registerAction2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -371,13 +371,13 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction{ label: languageName, - iconClasses: getIconClasses(modelService, modeService, this.getFakeResource(languageName, modeService)), + iconClasses: getIconClasses(modelService, languageService, this.getFakeResource(languageName, languageService)), description, languageId }; @@ -459,14 +459,14 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction { const idx = context.notebookEditor.getCellIndex(context.cell); - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const newFocusMode = context.cell.focusMode === CellFocusMode.Editor ? 'editor' : 'container'; - const newCell = insertCell(modeService, context.notebookEditor, idx, context.cell.cellKind, 'below'); + const newCell = insertCell(languageService, context.notebookEditor, idx, context.cell.cellKind, 'below'); if (newCell) { context.notebookEditor.focusNotebookCell(newCell, newFocusMode); } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts index c399b50cd417e..7e8afe68e4d74 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts @@ -5,7 +5,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { localize } from 'vs/nls'; import { IAction2Options, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -43,14 +43,14 @@ abstract class InsertCellCommand extends NotebookAction { context.notebookEditor.focus(); } - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); if (context.cell) { const idx = context.notebookEditor.getCellIndex(context.cell); - newCell = insertCell(modeService, context.notebookEditor, idx, this.kind, this.direction, undefined, true); + newCell = insertCell(languageService, context.notebookEditor, idx, this.kind, this.direction, undefined, true); } else { const focusRange = context.notebookEditor.getFocus(); const next = Math.max(focusRange.end - 1, 0); - newCell = insertCell(modeService, context.notebookEditor, next, this.kind, this.direction, undefined, true); + newCell = insertCell(languageService, context.notebookEditor, next, this.kind, this.direction, undefined, true); } if (newCell) { @@ -185,8 +185,8 @@ registerAction2(class InsertCodeCellAtTopAction extends NotebookAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - const modeService = accessor.get(IModeService); - const newCell = insertCell(modeService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); + const languageService = accessor.get(ILanguageService); + const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); if (newCell) { context.notebookEditor.focusNotebookCell(newCell, 'editor'); @@ -212,8 +212,8 @@ registerAction2(class InsertMarkdownCellAtTopAction extends NotebookAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - const modeService = accessor.get(IModeService); - const newCell = insertCell(modeService, context.notebookEditor, 0, CellKind.Markup, 'above', undefined, true); + const languageService = accessor.get(ILanguageService); + const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Markup, 'above', undefined, true); if (newCell) { context.notebookEditor.focusNotebookCell(newCell, 'editor'); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 17955e7c26160..dcdc3d0f93558 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -13,7 +13,7 @@ import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, Dif import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { CellEditType, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -303,7 +303,7 @@ abstract class AbstractElementRenderer extends Disposable { readonly templateData: CellDiffSingleSideRenderTemplate | CellDiffSideBySideRenderTemplate, readonly style: 'left' | 'right' | 'full', protected readonly instantiationService: IInstantiationService, - protected readonly modeService: IModeService, + protected readonly languageService: ILanguageService, protected readonly modelService: IModelService, protected readonly textModelService: ITextModelService, protected readonly contextMenuService: IContextMenuService, @@ -596,7 +596,7 @@ abstract class AbstractElementRenderer extends Disposable { this.layout({ metadataHeight: true }); this._metadataEditorDisposeStore.add(this._metadataEditor); - const mode = this.modeService.create('jsonc'); + const mode = this.languageService.create('jsonc'); const originalMetadataSource = getFormatedMetadataJSON(this.notebookEditor.textModel!, this.cell.type === 'insert' ? this.cell.modified!.metadata || {} @@ -630,7 +630,7 @@ abstract class AbstractElementRenderer extends Disposable { const originalOutputsSource = getFormatedOutputJSON(this.cell.original?.outputs || []); const modifiedOutputsSource = getFormatedOutputJSON(this.cell.modified?.outputs || []); if (originalOutputsSource !== modifiedOutputsSource) { - const mode = this.modeService.create('json'); + const mode = this.languageService.create('json'); const originalModel = this.modelService.createModel(originalOutputsSource, mode, undefined, true); const modifiedModel = this.modelService.createModel(modifiedOutputsSource, mode, undefined, true); this._outputEditorDisposeStore.add(originalModel); @@ -690,7 +690,7 @@ abstract class AbstractElementRenderer extends Disposable { }, {}); this._outputEditorDisposeStore.add(this._outputEditor); - const mode = this.modeService.create('json'); + const mode = this.languageService.create('json'); const originaloutputSource = getFormatedOutputJSON( this.notebookEditor.textModel!.transientOptions.transientOutputs ? [] @@ -755,7 +755,7 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { templateData: CellDiffSingleSideRenderTemplate, style: 'left' | 'right' | 'full', instantiationService: IInstantiationService, - modeService: IModeService, + languageService: ILanguageService, modelService: IModelService, textModelService: ITextModelService, contextMenuService: IContextMenuService, @@ -771,7 +771,7 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { templateData, style, instantiationService, - modeService, + languageService, modelService, textModelService, contextMenuService, @@ -946,7 +946,7 @@ export class DeletedElement extends SingleSideDiffElement { notebookEditor: INotebookTextDiffEditor, cell: SingleSideDiffElementViewModel, templateData: CellDiffSingleSideRenderTemplate, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IModelService modelService: IModelService, @ITextModelService textModelService: ITextModelService, @IInstantiationService instantiationService: IInstantiationService, @@ -958,7 +958,7 @@ export class DeletedElement extends SingleSideDiffElement { @IConfigurationService configurationService: IConfigurationService, ) { - super(notebookEditor, cell, templateData, 'left', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'left', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); } styleContainer(container: HTMLElement) { @@ -1099,7 +1099,7 @@ export class InsertElement extends SingleSideDiffElement { cell: SingleSideDiffElementViewModel, templateData: CellDiffSingleSideRenderTemplate, @IInstantiationService instantiationService: IInstantiationService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IModelService modelService: IModelService, @ITextModelService textModelService: ITextModelService, @IContextMenuService contextMenuService: IContextMenuService, @@ -1109,7 +1109,7 @@ export class InsertElement extends SingleSideDiffElement { @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, ) { - super(notebookEditor, cell, templateData, 'right', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'right', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); } styleContainer(container: HTMLElement): void { @@ -1257,7 +1257,7 @@ export class ModifiedElement extends AbstractElementRenderer { cell: SideBySideDiffElementViewModel, templateData: CellDiffSideBySideRenderTemplate, @IInstantiationService instantiationService: IInstantiationService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IModelService modelService: IModelService, @ITextModelService textModelService: ITextModelService, @IContextMenuService contextMenuService: IContextMenuService, @@ -1267,7 +1267,7 @@ export class ModifiedElement extends AbstractElementRenderer { @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, ) { - super(notebookEditor, cell, templateData, 'full', instantiationService, modeService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'full', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); this.cell = cell; this.templateData = templateData; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index b3b86b4dad0ca..bb2c3af37d0f3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -13,7 +13,7 @@ import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; import { ITextModel, ITextBufferFactory, DefaultEndOfLine, ITextBuffer } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageSelection, ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { Extensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -238,7 +238,7 @@ class CellContentProvider implements ITextModelContentProvider { constructor( @ITextModelService textModelService: ITextModelService, @IModelService private readonly _modelService: IModelService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, ) { this._registration = textModelService.registerTextModelContentProvider(CellUri.scheme, this); @@ -274,8 +274,8 @@ class CellContentProvider implements ITextModelContentProvider { return cell.textBuffer.getLineContent(1).substr(0, limit); } }; - const languageId = this._modeService.getModeIdForLanguageName(cell.language); - const languageSelection = languageId ? this._modeService.create(languageId) : (cell.cellKind === CellKind.Markup ? this._modeService.create('markdown') : this._modeService.createByFilepathOrFirstLine(resource, cell.textBuffer.getLineContent(1))); + const languageId = this._languageService.getModeIdForLanguageName(cell.language); + const languageSelection = languageId ? this._languageService.create(languageId) : (cell.cellKind === CellKind.Markup ? this._languageService.create('markdown') : this._languageService.createByFilepathOrFirstLine(resource, cell.textBuffer.getLineContent(1))); result = this._modelService.createModel( bufferFactory, languageSelection, @@ -302,7 +302,7 @@ class CellInfoContentProvider { constructor( @ITextModelService textModelService: ITextModelService, @IModelService private readonly _modelService: IModelService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @ILabelService private readonly _labelService: ILabelService, @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, ) { @@ -349,7 +349,7 @@ class CellInfoContentProvider { const ref = await this._notebookModelResolverService.resolve(data.notebook); let result: ITextModel | null = null; - const mode = this._modeService.create('json'); + const mode = this._languageService.create('json'); for (const cell of ref.object.notebook.cells) { if (cell.handle === data.handle) { @@ -382,7 +382,7 @@ class CellInfoContentProvider { if (streamOutputData) { return { content: streamOutputData, - mode: this._modeService.create('plaintext') + mode: this._languageService.create('plaintext') }; } @@ -396,7 +396,7 @@ class CellInfoContentProvider { }, cell: NotebookCellTextModel) { let result: { content: string, mode: ILanguageSelection } | undefined = undefined; - const mode = this._modeService.create('json'); + const mode = this._languageService.create('json'); const op = cell.outputs.find(op => op.outputId === data.outputId); const streamOutputData = this.parseStreamOutput(op); if (streamOutputData) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index a877084617e22..fe8c27d8c94a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -13,7 +13,7 @@ import { IDimension } from 'vs/editor/common/editorCommon'; import { IReadonlyTextBuffer } from 'vs/editor/common/model'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -44,7 +44,7 @@ export class CodeCell extends Disposable { @INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService, @IKeybindingService readonly keybindingService: IKeybindingService, @IOpenerService readonly openerService: IOpenerService, - @IModeService readonly modeService: IModeService, + @ILanguageService readonly languageService: ILanguageService, ) { super(); @@ -381,7 +381,7 @@ export class CodeCell extends Disposable { } private _getRichText(buffer: IReadonlyTextBuffer, language: string) { - return tokenizeToString(buffer.getLineContent(1), this.modeService.languageIdCodec, TokenizationRegistry.get(language)!); + return tokenizeToString(buffer.getLineContent(1), this.languageService.languageIdCodec, TokenizationRegistry.get(language)!); } private _removeInputCollapsePreview() { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts index 17bc0d1300200..5eaacecb6dd0c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts @@ -24,7 +24,7 @@ import { IReadonlyTextBuffer } from 'vs/editor/common/model'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; @@ -53,7 +53,7 @@ export class StatefulMarkdownCell extends Disposable { @IContextKeyService private readonly contextKeyService: IContextKeyService, @INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IConfigurationService private configurationService: IConfigurationService, ) { super(); @@ -254,7 +254,7 @@ export class StatefulMarkdownCell extends Disposable { } private getRichText(buffer: IReadonlyTextBuffer, language: string) { - return tokenizeToString(buffer.getLineContent(1), this.modeService.languageIdCodec, TokenizationRegistry.get(language)!); + return tokenizeToString(buffer.getLineContent(1), this.languageService.languageIdCodec, TokenizationRegistry.get(language)!); } private viewUpdateEditing(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 4625aac3e8c7a..8697cf35e98ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -18,7 +18,7 @@ import * as UUID from 'vs/base/common/uuid'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import * as nls from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -130,7 +130,7 @@ export class BackLayerWebView extends Disposable { @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, ) { super(); @@ -760,17 +760,17 @@ var requirejs = (function() { for (const { id, value, lang } of data.codeBlocks) { // The language id may be a language aliases (e.g.js instead of javascript) - const languageId = this.modeService.getModeIdForLanguageName(lang); + const languageId = this.languageService.getModeIdForLanguageName(lang); if (!languageId) { continue; } - this.modeService.triggerMode(languageId); + this.languageService.triggerMode(languageId); TokenizationRegistry.getPromise(languageId)?.then(tokenization => { if (this._disposed) { return; } - const html = tokenizeToString(value, this.modeService.languageIdCodec, tokenization); + const html = tokenizeToString(value, this.languageService.languageIdCodec, tokenization); this._sendMessageToWebview({ type: 'tokenizedCodeBlock', html, diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index dff3b9cb2f314..625fe9ea3998d 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -13,7 +13,7 @@ import * as model from 'vs/editor/common/model'; import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel'; import { CellInternalMetadataChangedEvent, CellKind, ICell, ICellOutput, IOutputDto, IOutputItemDto, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -79,20 +79,20 @@ export class NotebookCellTextModel extends Disposable implements ICell { set language(newLanguage: string) { if (this._textModel // 1. the language update is from workspace edit, checking if it's the same as text model's mode - && this._textModel.getLanguageId() === this._modeService.getModeIdForLanguageName(newLanguage) + && this._textModel.getLanguageId() === this._languageService.getModeIdForLanguageName(newLanguage) // 2. the text model's mode might be the same as the `this.language`, even if the language friendly name is not the same, we should not trigger an update - && this._textModel.getLanguageId() === this._modeService.getModeIdForLanguageName(this.language)) { + && this._textModel.getLanguageId() === this._languageService.getModeIdForLanguageName(this.language)) { return; } - const newMode = this._modeService.getModeIdForLanguageName(newLanguage); + const newMode = this._languageService.getModeIdForLanguageName(newLanguage); if (newMode === null) { return; } if (this._textModel) { - const languageId = this._modeService.create(newMode); + const languageId = this._languageService.create(newMode); this._textModel.setMode(languageId.languageId); } @@ -168,7 +168,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { // Init language from text model // The language defined in the cell might not be supported in the editor so the text model might be using the default fallback // If so let's not modify the language - if (!(this._modeService.getModeId(this.language) === null && (this._textModel.getLanguageId() === 'plaintext' || this._textModel.getLanguageId() === 'jupyter'))) { + if (!(this._languageService.getModeId(this.language) === null && (this._textModel.getLanguageId() === 'plaintext' || this._textModel.getLanguageId() === 'jupyter'))) { this.language = this._textModel.getLanguageId(); } @@ -201,7 +201,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { metadata: NotebookCellMetadata | undefined, internalMetadata: NotebookCellInternalMetadata | undefined, public readonly transientOptions: TransientOptions, - private readonly _modeService: IModeService + private readonly _languageService: ILanguageService ) { super(); this._outputs = outputs.map(op => new NotebookCellOutputTextModel(op)); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 735d018f7fe11..6e7631e670bdc 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -17,7 +17,7 @@ import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/commo import { IModelService } from 'vs/editor/common/services/modelService'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { isDefined } from 'vs/base/common/types'; @@ -209,7 +209,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel options: TransientOptions, @IUndoRedoService private readonly _undoService: IUndoRedoService, @IModelService private readonly _modelService: IModelService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, ) { super(); this.transientOptions = options; @@ -277,7 +277,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const mainCells = cells.map(cell => { const cellHandle = this._cellhandlePool++; const cellUri = CellUri.generate(this.uri, cellHandle); - return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.mime, cell.cellKind, cell.outputs, cell.metadata, cell.internalMetadata, this.transientOptions, this._modeService); + return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.mime, cell.cellKind, cell.outputs, cell.metadata, cell.internalMetadata, this.transientOptions, this._languageService); }); for (let i = 0; i < mainCells.length; i++) { @@ -601,7 +601,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const cell = new NotebookCellTextModel( cellUri, cellHandle, cellDto.source, cellDto.language, cellDto.mime, cellDto.cellKind, cellDto.outputs || [], cellDto.metadata, cellDto.internalMetadata, this.transientOptions, - this._modeService + this._languageService ); const textModel = this._modelService.getModel(cellUri); if (textModel && textModel instanceof TextModel) { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index f947237b07956..5cd0ed5c1e441 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { CellKind, CellUri, diff, MimeTypeDisplayOrder, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { cellIndexesToRanges, cellRangesToIndexes, reduceCellRanges } from 'vs/workbench/contrib/notebook/common/notebookRange'; @@ -16,12 +16,12 @@ import { setupInstantiationService, TestCell } from 'vs/workbench/contrib/notebo suite('NotebookCommon', () => { let disposables: DisposableStore; let instantiationService: TestInstantiationService; - let modeService: IModeService; + let languageService: ILanguageService; suiteSetup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); - modeService = instantiationService.get(IModeService); + languageService = instantiationService.get(ILanguageService); }); suiteTeardown(() => disposables.dispose()); @@ -231,7 +231,7 @@ suite('NotebookCommon', () => { for (let i = 0; i < 5; i++) { cells.push( - new TestCell('notebook', i, `var a = ${i};`, 'javascript', CellKind.Code, [], modeService) + new TestCell('notebook', i, `var a = ${i};`, 'javascript', CellKind.Code, [], languageService) ); } @@ -257,8 +257,8 @@ suite('NotebookCommon', () => { ] ); - const cellA = new TestCell('notebook', 6, 'var a = 6;', 'javascript', CellKind.Code, [], modeService); - const cellB = new TestCell('notebook', 7, 'var a = 7;', 'javascript', CellKind.Code, [], modeService); + const cellA = new TestCell('notebook', 6, 'var a = 6;', 'javascript', CellKind.Code, [], languageService); + const cellB = new TestCell('notebook', 7, 'var a = 7;', 'javascript', CellKind.Code, [], languageService); const modifiedCells = [ cells[0], diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts index 9381405d7c823..f7f84f4b467df 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -27,12 +27,12 @@ suite('NotebookCellList focus/selection', () => { let disposables: DisposableStore; let instantiationService: TestInstantiationService; - let modeService: IModeService; + let languageService: ILanguageService; suiteSetup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); - modeService = instantiationService.get(IModeService); + languageService = instantiationService.get(ILanguageService); }); suiteTeardown(() => disposables.dispose()); @@ -219,8 +219,8 @@ suite('NotebookCellList focus/selection', () => { // mimic undo editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 0, cells: [ - new TestCell(viewModel.viewType, 7, '# header f', 'markdown', CellKind.Code, [], modeService), - new TestCell(viewModel.viewType, 8, 'var g = 5;', 'javascript', CellKind.Code, [], modeService) + new TestCell(viewModel.viewType, 7, '# header f', 'markdown', CellKind.Code, [], languageService), + new TestCell(viewModel.viewType, 8, 'var g = 5;', 'javascript', CellKind.Code, [], languageService) ] }], true, undefined, () => undefined, undefined, false); viewModel.updateFoldingRanges(foldingModel.regions); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts index e2253a7544bf8..1da7a3e5109e2 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { CellEditType, CellKind, ICellEditOperation, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -16,12 +16,12 @@ import { setupInstantiationService, TestCell, valueBytesFromString, withTestNote suite('NotebookTextModel', () => { let disposables: DisposableStore; let instantiationService: TestInstantiationService; - let modeService: IModeService; + let languageService: ILanguageService; suiteSetup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); - modeService = instantiationService.get(IModeService); + languageService = instantiationService.get(ILanguageService); instantiationService.spy(IUndoRedoService, 'pushElement'); }); @@ -38,8 +38,8 @@ suite('NotebookTextModel', () => { (editor) => { const textModel = editor.textModel; textModel.applyEdits([ - { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], modeService)] }, - { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], modeService)] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] }, + { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService)] }, ], true, undefined, () => undefined, undefined); assert.strictEqual(textModel.cells.length, 6); @@ -61,8 +61,8 @@ suite('NotebookTextModel', () => { (editor) => { const textModel = editor.textModel; textModel.applyEdits([ - { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], modeService)] }, - { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], modeService)] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService)] }, ], true, undefined, () => undefined, undefined); assert.strictEqual(textModel.cells.length, 6); @@ -106,7 +106,7 @@ suite('NotebookTextModel', () => { const textModel = editor.textModel; textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, - { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], modeService)] }, + { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] }, ], true, undefined, () => undefined, undefined); assert.strictEqual(textModel.cells.length, 4); @@ -128,7 +128,7 @@ suite('NotebookTextModel', () => { const textModel = editor.textModel; textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, - { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], modeService)] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] }, ], true, undefined, () => undefined, undefined); assert.strictEqual(textModel.cells.length, 4); @@ -150,7 +150,7 @@ suite('NotebookTextModel', () => { (editor) => { const textModel = editor.textModel; textModel.applyEdits([ - { editType: CellEditType.Replace, index: 1, count: 1, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], modeService)] }, + { editType: CellEditType.Replace, index: 1, count: 1, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] }, ], true, undefined, () => undefined, undefined); assert.strictEqual(textModel.cells.length, 4); @@ -402,7 +402,7 @@ suite('NotebookTextModel', () => { textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, - { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], modeService)] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] }, ], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined); assert.strictEqual(textModel.cells.length, 4); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index 7b7346c2dae4e..80bb2747e4064 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -34,7 +34,7 @@ suite('NotebookViewModel', () => { let bulkEditService: IBulkEditService; let undoRedoService: IUndoRedoService; let modelService: IModelService; - let modeService: IModeService; + let languageService: ILanguageService; suiteSetup(() => { disposables = new DisposableStore(); @@ -43,7 +43,7 @@ suite('NotebookViewModel', () => { bulkEditService = instantiationService.get(IBulkEditService); undoRedoService = instantiationService.get(IUndoRedoService); modelService = instantiationService.get(IModelService); - modeService = instantiationService.get(IModeService); + languageService = instantiationService.get(ILanguageService); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); @@ -52,7 +52,7 @@ suite('NotebookViewModel', () => { suiteTeardown(() => disposables.dispose()); test('ctor', function () { - const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false }, undoRedoService, modelService, modeService); + const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false }, undoRedoService, modelService, languageService); const model = new NotebookEditorTestModel(notebook); const viewContext = new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService)), new NotebookEventDispatcher()); const viewModel = new NotebookViewModel('notebook', model.notebook, viewContext, null, { isReadOnly: false }, instantiationService, bulkEditService, undoRedoService, textModelService); diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index ee0b96214cb9c..b8f0b300d0344 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -17,7 +17,7 @@ import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; @@ -63,9 +63,9 @@ export class TestCell extends NotebookCellTextModel { language: string, cellKind: CellKind, outputs: IOutputDto[], - modeService: IModeService, + languageService: ILanguageService, ) { - super(CellUri.generate(URI.parse('test:///fake/notebook'), handle), handle, source, language, Mimes.text, cellKind, outputs, undefined, undefined, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false }, modeService); + super(CellUri.generate(URI.parse('test:///fake/notebook'), handle), handle, source, language, Mimes.text, cellKind, outputs, undefined, undefined, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false }, languageService); } } @@ -158,7 +158,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi export function setupInstantiationService(disposables = new DisposableStore()) { const instantiationService = new TestInstantiationService(); - instantiationService.stub(IModeService, disposables.add(new ModeServiceImpl())); + instantiationService.stub(ILanguageService, disposables.add(new ModeServiceImpl())); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 99b4a96ace168..59a4f7934f566 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { Promises, ThrottledDelayer } from 'vs/base/common/async'; import { IFileService } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { Disposable, toDisposable, IDisposable, dispose, MutableDisposable } from 'vs/base/common/lifecycle'; import { isNumber } from 'vs/base/common/types'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -110,7 +110,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel private readonly file: URI, @IFileService private readonly fileService: IFileService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @ILogService logService: ILogService, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, ) { @@ -163,7 +163,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel if (this.model) { this.model.setValue(content); } else { - this.model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); + this.model = this.modelService.createModel(content, this.languageService.create(this.mimeType), this.modelUri); this.fileHandler.watch(this.etag); const disposable = this.model.onWillDispose(() => { this.cancelModelUpdate(); @@ -328,12 +328,12 @@ class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutpu file: URI, @IFileService fileService: IFileService, @IModelService modelService: IModelService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @ILoggerService loggerService: ILoggerService, @ILogService logService: ILogService, @IEditorWorkerService editorWorkerService: IEditorWorkerService ) { - super(modelUri, mimeType, file, fileService, modelService, modeService, logService, editorWorkerService); + super(modelUri, mimeType, file, fileService, modelService, languageService, logService, editorWorkerService); // Donot rotate to check for the file reset this.logger = loggerService.createLogger(file, { always: true, donotRotate: true, donotUseFormatters: true }); diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index e4fc0e6573e0f..9a26eccdde376 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -9,7 +9,7 @@ import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResource import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { ITextModel } from 'vs/editor/common/model'; import { ILifecycleService, LifecyclePhase, StartupKindToString } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; @@ -82,7 +82,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { constructor( @IModelService private readonly _modelService: IModelService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @ICodeEditorService private readonly _editorService: ICodeEditorService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @ITimerService private readonly _timerService: ITimerService, @@ -94,7 +94,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { if (!this._model || this._model.isDisposed()) { dispose(this._modelDisposables); - const langId = this._modeService.create('markdown'); + const langId = this._languageService.create('markdown'); this._model = this._modelService.getModel(resource) || this._modelService.createModel('Loading...', langId, resource); this._modelDisposables.push(langId.onDidChange(e => { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index 699d4847c9cb2..036fa1175b73d 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -7,7 +7,7 @@ import { Action } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import * as nls from 'vs/nls'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -21,7 +21,7 @@ export class ConfigureLanguageBasedSettingsAction extends Action { id: string, label: string, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IPreferencesService private readonly preferencesService: IPreferencesService ) { @@ -29,23 +29,23 @@ export class ConfigureLanguageBasedSettingsAction extends Action { } override async run(): Promise { - const languages = this.modeService.getRegisteredLanguageNames(); + const languages = this.languageService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { - const description: string = nls.localize('languageDescriptionConfigured', "({0})", this.modeService.getModeIdForLanguageName(lang.toLowerCase())); + const description: string = nls.localize('languageDescriptionConfigured', "({0})", this.languageService.getModeIdForLanguageName(lang.toLowerCase())); // construct a fake resource to be able to show nice icons if any let fakeResource: URI | undefined; - const extensions = this.modeService.getExtensions(lang); + const extensions = this.languageService.getExtensions(lang); if (extensions && extensions.length) { fakeResource = URI.file(extensions[0]); } else { - const filenames = this.modeService.getFilenames(lang); + const filenames = this.languageService.getFilenames(lang); if (filenames && filenames.length) { fakeResource = URI.file(filenames[0]); } } return { label: lang, - iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource), + iconClasses: getIconClasses(this.modelService, this.languageService, fakeResource), description } as IQuickPickItem; }); @@ -53,7 +53,7 @@ export class ConfigureLanguageBasedSettingsAction extends Action { await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language") }) .then(pick => { if (pick) { - const languageId = this.modeService.getModeIdForLanguageName(pick.label.toLowerCase()); + const languageId = this.languageService.getModeIdForLanguageName(pick.label.toLowerCase()); if (typeof languageId === 'string') { return this.preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: `[${languageId}]`, edit: true } }); } diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index db3876cb3fc94..09eeb02a5a1c2 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -8,7 +8,7 @@ import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -35,7 +35,7 @@ export class PreferencesContribution implements IWorkbenchContribution { @IModelService private readonly modelService: IModelService, @ITextModelService private readonly textModelResolverService: ITextModelService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -122,7 +122,7 @@ export class PreferencesContribution implements IWorkbenchContribution { let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; if (schema) { const modelContent = JSON.stringify(schema); - const languageSelection = this.modeService.create('jsonc'); + const languageSelection = this.languageService.create('jsonc'); const model = this.modelService.createModel(modelContent, languageSelection, uri); const disposables = new DisposableStore(); disposables.add(schemaRegistry.onDidChangeSchema(schemaUri => { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 959fa58ed3fc4..2d1f8002d4d71 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -67,7 +67,7 @@ import { LinkDetector } from 'vs/editor/contrib/links/links'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ILabelService } from 'vs/platform/label/common/label'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; @@ -1649,7 +1649,7 @@ class SCMInputWidget extends Disposable { this.configurationService.updateValue('editor.wordBasedSuggestions', false, { resource: uri }, ConfigurationTarget.MEMORY); } - const textModel = this.modelService.getModel(uri) ?? this.modelService.createModel('', this.modeService.create('scminput'), uri); + const textModel = this.modelService.getModel(uri) ?? this.modelService.createModel('', this.languageService.create('scminput'), uri); this.inputEditor.setModel(textModel); // Validation @@ -1769,7 +1769,7 @@ class SCMInputWidget extends Disposable { overflowWidgetsDomNode: HTMLElement, @IContextKeyService contextKeyService: IContextKeyService, @IModelService private modelService: IModelService, - @IModeService private modeService: IModeService, + @ILanguageService private languageService: ILanguageService, @IKeybindingService private keybindingService: IKeybindingService, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 4db21dafdec05..d8074cc408e9c 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -23,7 +23,7 @@ import { DisposableStore, IDisposable, toDisposable, MutableDisposable, Disposab import { ILabelService } from 'vs/platform/label/common/label'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { localize } from 'vs/nls'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -171,7 +171,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { const openSideBySideDirection = configuration.openSideBySideDirection; const buttons: IQuickInputButton[] = []; diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index ae736ce77146e..2c2dd137cb83d 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -10,7 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; @@ -57,7 +57,7 @@ export class ReplacePreviewContentProvider implements ITextModelContentProvider, class ReplacePreviewModel extends Disposable { constructor( @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @ITextModelService private readonly textModelResolverService: ITextModelService, @IReplaceService private readonly replaceService: IReplaceService, @ISearchWorkbenchService private readonly searchWorkbenchService: ISearchWorkbenchService @@ -71,7 +71,7 @@ class ReplacePreviewModel extends Disposable { const ref = this._register(await this.textModelResolverService.createModelReference(fileResource)); const sourceModel = ref.object.textEditorModel; const sourceModelModeId = sourceModel.getLanguageId(); - const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.modeService.create(sourceModelModeId), replacePreviewUri); + const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.languageService.create(sourceModelModeId), replacePreviewUri); this._register(fileMatch.onChange(({ forceUpdateModel }) => this.update(sourceModel, replacePreviewModel, fileMatch, forceUpdateModel))); this._register(this.searchWorkbenchService.searchModel.onReplaceTermChanged(() => this.update(sourceModel, replacePreviewModel, fileMatch))); this._register(fileMatch.onDispose(() => replacePreviewModel.dispose())); // TODO@Sandeep we should not dispose a model directly but rather the reference (depends on https://github.com/microsoft/vscode/issues/17073) diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts index cb777a252fb7d..5017b442fe00a 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { parseSavedSearchEditor, parseSerializedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; @@ -49,7 +49,7 @@ class SearchEditorModelFactory { throw Error('Unable to contruct model for resource that already exists'); } - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const modelService = accessor.get(IModelService); const instantiationService = accessor.get(IInstantiationService); const workingCopyBackupService = accessor.get(IWorkingCopyBackupService); @@ -61,13 +61,13 @@ class SearchEditorModelFactory { if (!ongoingResolve) { ongoingResolve = (async () => { - const backup = await this.tryFetchModelFromBackupService(resource, modeService, modelService, workingCopyBackupService, instantiationService); + const backup = await this.tryFetchModelFromBackupService(resource, languageService, modelService, workingCopyBackupService, instantiationService); if (backup) { return backup; } return Promise.resolve({ - resultsModel: modelService.getModel(resource) ?? modelService.createModel('', modeService.create('search-result'), resource), + resultsModel: modelService.getModel(resource) ?? modelService.createModel('', languageService.create('search-result'), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -82,7 +82,7 @@ class SearchEditorModelFactory { throw Error('Unable to contruct model for resource that already exists'); } - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const modelService = accessor.get(IModelService); const instantiationService = accessor.get(IInstantiationService); const workingCopyBackupService = accessor.get(IWorkingCopyBackupService); @@ -94,13 +94,13 @@ class SearchEditorModelFactory { if (!ongoingResolve) { ongoingResolve = (async () => { - const backup = await this.tryFetchModelFromBackupService(resource, modeService, modelService, workingCopyBackupService, instantiationService); + const backup = await this.tryFetchModelFromBackupService(resource, languageService, modelService, workingCopyBackupService, instantiationService); if (backup) { return backup; } return Promise.resolve({ - resultsModel: modelService.createModel(contents ?? '', modeService.create('search-result'), resource), + resultsModel: modelService.createModel(contents ?? '', languageService.create('search-result'), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -115,7 +115,7 @@ class SearchEditorModelFactory { throw Error('Unable to contruct model for resource that already exists'); } - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const modelService = accessor.get(IModelService); const instantiationService = accessor.get(IInstantiationService); const workingCopyBackupService = accessor.get(IWorkingCopyBackupService); @@ -127,14 +127,14 @@ class SearchEditorModelFactory { if (!ongoingResolve) { ongoingResolve = (async () => { - const backup = await this.tryFetchModelFromBackupService(resource, modeService, modelService, workingCopyBackupService, instantiationService); + const backup = await this.tryFetchModelFromBackupService(resource, languageService, modelService, workingCopyBackupService, instantiationService); if (backup) { return backup; } const { text, config } = await instantiationService.invokeFunction(parseSavedSearchEditor, existingFile); return ({ - resultsModel: modelService.createModel(text ?? '', modeService.create('search-result'), resource), + resultsModel: modelService.createModel(text ?? '', languageService.create('search-result'), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -144,14 +144,14 @@ class SearchEditorModelFactory { }); } - private async tryFetchModelFromBackupService(resource: URI, modeService: IModeService, modelService: IModelService, workingCopyBackupService: IWorkingCopyBackupService, instantiationService: IInstantiationService): Promise { + private async tryFetchModelFromBackupService(resource: URI, languageService: ILanguageService, modelService: IModelService, workingCopyBackupService: IWorkingCopyBackupService, instantiationService: IInstantiationService): Promise { const backup = await workingCopyBackupService.resolve({ resource, typeId: SearchEditorWorkingCopyTypeId }); let model = modelService.getModel(resource); if (!model && backup) { const factory = await createTextBufferFactoryFromStream(backup.value); - model = modelService.createModel(factory, modeService.create('search-result'), resource); + model = modelService.createModel(factory, languageService.create('search-result'), resource); } if (model) { @@ -159,7 +159,7 @@ class SearchEditorModelFactory { const { text, config } = parseSerializedSearchEditor(existingFile); modelService.destroyModel(resource); return ({ - resultsModel: modelService.createModel(text ?? '', modeService.create('search-result'), resource), + resultsModel: modelService.createModel(text ?? '', languageService.create('search-result'), resource), configurationModel: new SearchConfigurationModel(config) }); } diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index d2e10650d03db..e352adb6bbf75 100644 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { extname } from 'vs/base/common/path'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -33,7 +33,7 @@ interface ISnippetPick extends IQuickPickItem { hint?: true; } -async function computePicks(snippetService: ISnippetsService, envService: IEnvironmentService, modeService: IModeService) { +async function computePicks(snippetService: ISnippetsService, envService: IEnvironmentService, languageService: ILanguageService) { const existing: ISnippetPick[] = []; const future: ISnippetPick[] = []; @@ -55,7 +55,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir const names = new Set(); outer: for (const snippet of file.data) { for (const scope of snippet.scopes) { - const name = modeService.getLanguageName(scope); + const name = languageService.getLanguageName(scope); if (name) { if (names.size >= 4) { names.add(`${name}...`); @@ -80,7 +80,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir const mode = basename(file.location).replace(/\.json$/, ''); existing.push({ label: basename(file.location), - description: `(${modeService.getLanguageName(mode)})`, + description: `(${languageService.getLanguageName(mode)})`, filepath: file.location }); seen.add(mode); @@ -88,8 +88,8 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir } const dir = envService.snippetsHome; - for (const mode of modeService.getRegisteredModes()) { - const label = modeService.getLanguageName(mode); + for (const mode of languageService.getRegisteredModes()) { + const label = languageService.getLanguageName(mode); if (label && !seen.has(mode)) { future.push({ label: mode, @@ -206,13 +206,13 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise => { const snippetService = accessor.get(ISnippetsService); const quickInputService = accessor.get(IQuickInputService); const opener = accessor.get(IOpenerService); - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const envService = accessor.get(IEnvironmentService); const workspaceService = accessor.get(IWorkspaceContextService); const fileService = accessor.get(IFileService); const textFileService = accessor.get(ITextFileService); - const picks = await computePicks(snippetService, envService, modeService); + const picks = await computePicks(snippetService, envService, languageService); const existing: QuickPickInput[] = picks.existing; type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index 7e0479f2f244e..e47fee2c30da3 100644 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { NULL_MODE_ID } from 'vs/editor/common/modes/nullMode'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; @@ -80,7 +80,7 @@ class InsertSnippetAction extends EditorAction { } async run(accessor: ServicesAccessor, editor: ICodeEditor, arg: any): Promise { - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const snippetService = accessor.get(ISnippetsService); if (!editor.hasModel()) { @@ -109,7 +109,7 @@ class InsertSnippetAction extends EditorAction { let languageId = NULL_MODE_ID; if (langId) { - const otherLangId = modeService.validateLanguageId(langId); + const otherLangId = languageService.validateLanguageId(langId); if (otherLangId) { languageId = otherLangId; } @@ -120,7 +120,7 @@ class InsertSnippetAction extends EditorAction { // validate the `languageId` to ensure this is a user // facing language with a name and the chance to have // snippets, else fall back to the outer language - if (!modeService.getLanguageName(languageId)) { + if (!languageService.getLanguageName(languageId)) { languageId = editor.getModel().getLanguageId(); } } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 93118f64abffa..e9b46b6e930b9 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -9,7 +9,7 @@ import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, CompletionItemInsertTextRule, CompletionContext, CompletionTriggerKind, CompletionItemLabel } from 'vs/editor/common/modes'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; import { localize } from 'vs/nls'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; @@ -57,7 +57,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider { readonly _debugDisplayName = 'snippetCompletions'; constructor( - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @ISnippetsService private readonly _snippets: ISnippetsService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService ) { @@ -166,8 +166,8 @@ export class SnippetCompletionProvider implements CompletionItemProvider { // snippets, else fall back to the outer language model.tokenizeIfCheap(position.lineNumber); let languageId: string | null = model.getLanguageIdAtPosition(position.lineNumber, position.column); - languageId = this._modeService.validateLanguageId(languageId); - if (!languageId || !this._modeService.getLanguageName(languageId)) { + languageId = this._languageService.validateLanguageId(languageId); + if (!languageId || !this._languageService.getLanguageName(languageId)) { languageId = model.getLanguageId(); } return languageId; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 526973df0a7e0..334ac214861b7 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -9,7 +9,7 @@ import * as resources from 'vs/base/common/resources'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { setSnippetSuggestSupport } from 'vs/editor/contrib/suggest/suggest'; import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -43,7 +43,7 @@ namespace snippetExt { location: URI; } - export function toValidSnippet(extension: IExtensionPointUser, snippet: ISnippetsExtensionPoint, modeService: IModeService): IValidSnippetsExtensionPoint | null { + export function toValidSnippet(extension: IExtensionPointUser, snippet: ISnippetsExtensionPoint, languageService: ILanguageService): IValidSnippetsExtensionPoint | null { if (isFalsyOrWhitespace(snippet.path)) { extension.collector.error(localize( @@ -63,7 +63,7 @@ namespace snippetExt { return null; } - if (!isFalsyOrWhitespace(snippet.language) && !modeService.isRegisteredMode(snippet.language)) { + if (!isFalsyOrWhitespace(snippet.language) && !languageService.isRegisteredMode(snippet.language)) { extension.collector.error(localize( 'invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", @@ -178,7 +178,7 @@ class SnippetsService implements ISnippetsService { constructor( @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @ILogService private readonly _logService: ILogService, @IFileService private readonly _fileService: IFileService, @ITextFileService private readonly _textfileService: ITextFileService, @@ -192,7 +192,7 @@ class SnippetsService implements ISnippetsService { this._initWorkspaceSnippets(); }))); - setSnippetSuggestSupport(new SnippetCompletionProvider(this._modeService, this, new TestLanguageConfigurationService())); + setSnippetSuggestSupport(new SnippetCompletionProvider(this._languageService, this, new TestLanguageConfigurationService())); this._enablement = instantiationService.createInstance(SnippetEnablement); } @@ -228,7 +228,7 @@ class SnippetsService implements ISnippetsService { const result: Snippet[] = []; const promises: Promise[] = []; - const langName = this._modeService.validateLanguageId(languageId); + const langName = this._languageService.validateLanguageId(languageId); if (langName) { for (const file of this._files.values()) { promises.push(file.load() @@ -243,7 +243,7 @@ class SnippetsService implements ISnippetsService { getSnippetsSync(languageId: string, opts?: ISnippetGetOptions): Snippet[] { const result: Snippet[] = []; - const langName = this._modeService.validateLanguageId(languageId); + const langName = this._languageService.validateLanguageId(languageId); if (langName) { for (const file of this._files.values()) { // kick off loading (which is a noop in case it's already loaded) @@ -275,7 +275,7 @@ class SnippetsService implements ISnippetsService { for (const extension of extensions) { for (const contribution of extension.value) { - const validContribution = snippetExt.toValidSnippet(extension, contribution, this._modeService); + const validContribution = snippetExt.toValidSnippet(extension, contribution, this._languageService); if (!validContribution) { continue; } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index f2604ce9abbde..6111bbb112430 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -52,12 +52,12 @@ suite('SnippetsService', function () { }); let disposables: DisposableStore; - let modeService: ModeServiceImpl; + let languageService: ModeServiceImpl; let snippetService: ISnippetsService; setup(function () { disposables = new DisposableStore(); - modeService = disposables.add(new ModeServiceImpl()); + languageService = disposables.add(new ModeServiceImpl()); snippetService = new SimpleSnippetService([new Snippet( ['fooLang'], 'barTest', @@ -83,7 +83,7 @@ suite('SnippetsService', function () { test('snippet completions - simple', function () { - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -94,7 +94,7 @@ suite('SnippetsService', function () { test('snippet completions - simple 2', function () { - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('hello ', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 6), context)!.then(result => { @@ -105,7 +105,7 @@ suite('SnippetsService', function () { test('snippet completions - with prefix', function () { - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('bar', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 4), context)!.then(result => { @@ -140,7 +140,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('bar-bar', undefined, 'fooLang')); await provider.provideCompletionItems(model, new Position(1, 3), context)!.then(result => { @@ -210,7 +210,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('\t { @@ -245,7 +245,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('\n\t\n>/head>', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -275,7 +275,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('', undefined, 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -302,7 +302,7 @@ suite('SnippetsService', function () { '', SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('p-', undefined, 'fooLang')); @@ -327,7 +327,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; @@ -346,7 +346,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel(':', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; @@ -365,7 +365,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('template', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; @@ -388,7 +388,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; @@ -411,7 +411,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('.🐷-a-b', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 8), context)!; @@ -430,7 +430,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = disposables.add(createTextModel('a ', undefined, 'fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -457,7 +457,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel(' <', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -487,7 +487,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('not wordFoo bar', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -529,7 +529,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('filler e KEEP ng filler', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; @@ -560,7 +560,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('[psc]', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 5), context)!; @@ -585,7 +585,7 @@ suite('SnippetsService', function () { SnippetSource.User )]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel(' ci', undefined, 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 4), context)!; @@ -606,7 +606,7 @@ suite('SnippetsService', function () { // new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User) ]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('\'\'', undefined, 'fooLang'); let result = await provider.provideCompletionItems( @@ -628,7 +628,7 @@ suite('SnippetsService', function () { new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User) ]); - const provider = new SnippetCompletionProvider(modeService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); let model = createTextModel('\'\'', undefined, 'fooLang'); diff --git a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index f1e91e8f28b18..c50f3ed611af5 100644 --- a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { language } from 'vs/base/common/platform'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -29,7 +29,7 @@ class LanguageSurvey extends Disposable { storageService: IStorageService, notificationService: INotificationService, telemetryService: ITelemetryService, - modeService: IModeService, + languageService: ILanguageService, textFileService: ITextFileService, openerService: IOpenerService, productService: IProductService @@ -95,7 +95,7 @@ class LanguageSurvey extends Disposable { notificationService.prompt( Severity.Info, - localize('helpUs', "Help us improve our support for {0}", modeService.getLanguageName(data.languageId) ?? data.languageId), + localize('helpUs', "Help us improve our support for {0}", languageService.getLanguageName(data.languageId) ?? data.languageId), [{ label: localize('takeShortSurvey', "Take Short Survey"), run: () => { @@ -135,7 +135,7 @@ class LanguageSurveysContribution implements IWorkbenchContribution { @ITextFileService private readonly textFileService: ITextFileService, @IOpenerService private readonly openerService: IOpenerService, @IProductService private readonly productService: IProductService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IExtensionService private readonly extensionService: IExtensionService ) { this.handleSurveys(); @@ -154,7 +154,7 @@ class LanguageSurveysContribution implements IWorkbenchContribution { // Handle surveys this.productService.surveys .filter(surveyData => surveyData.surveyId && surveyData.editCount && surveyData.languageId && surveyData.surveyUrl && surveyData.userProbability) - .map(surveyData => new LanguageSurvey(surveyData, this.storageService, this.notificationService, this.telemetryService, this.modeService, this.textFileService, this.openerService, this.productService)); + .map(surveyData => new LanguageSurvey(surveyData, this.storageService, this.notificationService, this.telemetryService, this.languageService, this.textFileService, this.openerService, this.productService)); } } diff --git a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts index d678d2cbd317e..09b1b5794e35e 100644 --- a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts +++ b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageSelection, ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { TestMessageType } from 'vs/workbench/contrib/testing/common/testCollection'; @@ -20,7 +20,7 @@ import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResu export class TestingContentProvider implements IWorkbenchContribution, ITextModelContentProvider { constructor( @ITextModelService textModelResolverService: ITextModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IModelService private readonly modelService: IModelService, @ITestResultService private readonly resultService: ITestResultService, ) { @@ -66,7 +66,7 @@ export class TestingContentProvider implements IWorkbenchContribution, ITextMode text = message; } else if (message) { text = message.value; - language = this.modeService.create('markdown'); + language = this.languageService.create('markdown'); } break; } diff --git a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts index fda191dc5b0e6..d877b697925fd 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchThemeService, IWorkbenchColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -89,7 +89,7 @@ class ThemeDocument { class Snapper { constructor( - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @ITextMateService private readonly textMateService: ITextMateService ) { @@ -216,7 +216,7 @@ class Snapper { } public captureSyntaxTokens(fileName: string, content: string): Promise { - const languageId = this.modeService.getModeIdByFilepathOrFirstLine(URI.file(fileName)); + const languageId = this.languageService.getModeIdByFilepathOrFirstLine(URI.file(fileName)); return this.textMateService.createGrammar(languageId!).then((grammar) => { if (!grammar) { return []; diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index a3cdd412cc1b9..ea97969562d5b 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import * as nls from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -42,7 +42,7 @@ export class ReleaseNotesManager { public constructor( @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IOpenerService private readonly _openerService: IOpenerService, @IRequestService private readonly _requestService: IRequestService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -206,7 +206,7 @@ export class ReleaseNotesManager { private async renderBody(text: string) { const nonce = generateUuid(); - const content = await renderMarkdownDocument(text, this._extensionService, this._modeService, false); + const content = await renderMarkdownDocument(text, this._extensionService, this._languageService, false); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; return ` diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index fc1c3f164a5a6..f861857fe9a1c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -14,7 +14,7 @@ import { registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/ import type { IEditorContribution } from 'vs/editor/common/editorCommon'; import type { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; @@ -1287,13 +1287,13 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { constructor( @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, ) { } provideTextContent(uri: URI): Promise | null { if (uri.scheme === USER_DATA_SYNC_SCHEME) { - return this.userDataSyncService.resolveContent(uri).then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri)); + return this.userDataSyncService.resolveContent(uri).then(content => this.modelService.createModel(content || '', this.languageService.create('jsonc'), uri)); } return null; } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index 9a81e3426fe96..6caf4e5d77185 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -46,7 +46,7 @@ import { Link } from 'vs/platform/opener/browser/link'; import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { generateUuid } from 'vs/base/common/uuid'; import { TokenizationRegistry } from 'vs/editor/common/modes'; @@ -161,7 +161,7 @@ export class GettingStartedPage extends EditorPane { @IWalkthroughsService private readonly gettingStartedService: IWalkthroughsService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IFileService private readonly fileService: IFileService, @IOpenerService private readonly openerService: IOpenerService, @IThemeService themeService: IThemeService, @@ -471,7 +471,7 @@ export class GettingStartedPage extends EditorPane { return new Promise(resolve => { require([moduleId], content => { const markdown = content.default(); - resolve(renderMarkdownDocument(markdown, this.extensionService, this.modeService, true, true)); + resolve(renderMarkdownDocument(markdown, this.extensionService, this.languageService, true, true)); }); }); } @@ -500,7 +500,7 @@ export class GettingStartedPage extends EditorPane { : path); const markdown = bytes.value.toString(); - return renderMarkdownDocument(markdown, this.extensionService, this.modeService, true, true); + return renderMarkdownDocument(markdown, this.extensionService, this.languageService, true, true); } catch (e) { this.notificationService.error('Error reading markdown document at `' + path + '`: ' + e); return ''; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts index 7648efe5ae076..751aa6d35c460 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModel, DefaultEndOfLine, EndOfLinePreference, ITextBufferFactory } from 'vs/editor/common/model'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; @@ -44,7 +44,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi constructor( @ITextModelService private readonly textModelResolverService: ITextModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IModelService private readonly modelService: IModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { @@ -71,8 +71,8 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi const renderer = new marked.Renderer(); renderer.code = (code, lang) => { i++; - const languageId = this.modeService.getModeIdForLanguageName(lang) || ''; - const languageSelection = this.modeService.create(languageId); + const languageId = this.languageService.getModeIdForLanguageName(lang) || ''; + const languageSelection = this.languageService.create(languageId); // Create all models for this resource in one go... we'll need them all and we don't want to re-parse markdown each time const model = this.modelService.createModel(code, languageSelection, resource.with({ fragment: `${i}.${lang}` })); if (i === j) { codeEditorModel = model; } diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index a7962686b9347..84d99f82fcfaa 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -11,7 +11,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getZoomLevel } from 'vs/base/browser/browser'; import { FileKind } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IQuickInputService, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; @@ -201,7 +201,7 @@ abstract class BaseSwitchWindow extends Action2 { const quickInputService = accessor.get(IQuickInputService); const keybindingService = accessor.get(IKeybindingService); const modelService = accessor.get(IModelService); - const modeService = accessor.get(IModeService); + const languageService = accessor.get(ILanguageService); const nativeHostService = accessor.get(INativeHostService); const currentWindowId = nativeHostService.windowId; @@ -215,7 +215,7 @@ abstract class BaseSwitchWindow extends Action2 { payload: window.id, label: window.title, ariaLabel: window.dirty ? localize('windowDirtyAriaLabel', "{0}, window with unsaved changes", window.title) : window.title, - iconClasses: getIconClasses(modelService, modeService, resource, fileKind), + iconClasses: getIconClasses(modelService, languageService, resource, fileKind), description: (currentWindowId === window.id) ? localize('current', "Current Window") : undefined, buttons: currentWindowId !== window.id ? window.dirty ? [this.closeDirtyWindowAction] : [this.closeWindowAction] : undefined }; diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index 8346c98ce2946..ae55ab4c4af36 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -21,7 +21,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import Severity from 'vs/base/common/severity'; import { coalesce, distinct } from 'vs/base/common/arrays'; import { compareIgnoreCase, trim } from 'vs/base/common/strings'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { Schemas } from 'vs/base/common/network'; @@ -44,7 +44,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { @IFileService protected readonly fileService: IFileService, @IOpenerService protected readonly openerService: IOpenerService, @IDialogService protected readonly dialogService: IDialogService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IWorkspacesService private readonly workspacesService: IWorkspacesService, @ILabelService private readonly labelService: ILabelService, @IPathService private readonly pathService: IPathService, @@ -301,9 +301,9 @@ export abstract class AbstractFileDialogService implements IFileDialogService { const ext: string | undefined = defaultUri ? resources.extname(defaultUri) : undefined; let matchingFilter: IFilter | undefined; - const registeredLanguageNames = this.modeService.getRegisteredLanguageNames().sort((a, b) => compareIgnoreCase(a, b)); + const registeredLanguageNames = this.languageService.getRegisteredLanguageNames().sort((a, b) => compareIgnoreCase(a, b)); const registeredLanguageFilters: IFilter[] = coalesce(registeredLanguageNames.map(languageName => { - const extensions = this.modeService.getExtensions(languageName); + const extensions = this.languageService.getExtensions(languageName); if (!extensions || !extensions.length) { return null; } diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 00193e2a532d0..8a7c0a868672e 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -15,7 +15,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -135,7 +135,7 @@ export class SimpleFileDialog { @INotificationService private readonly notificationService: INotificationService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @IPathService protected readonly pathService: IPathService, @@ -985,9 +985,9 @@ export class SimpleFileDialog { if (stat.isDirectory) { const filename = resources.basename(fullPath); fullPath = resources.addTrailingPathSeparator(fullPath, this.separator); - return { label: filename, uri: fullPath, isFolder: true, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined, FileKind.FOLDER) }; + return { label: filename, uri: fullPath, isFolder: true, iconClasses: getIconClasses(this.modelService, this.languageService, fullPath || undefined, FileKind.FOLDER) }; } else if (!stat.isDirectory && this.allowFileSelection && this.filterFile(fullPath)) { - return { label: stat.name, uri: fullPath, isFolder: false, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined) }; + return { label: stat.name, uri: fullPath, isFolder: false, iconClasses: getIconClasses(this.modelService, this.languageService, fullPath || undefined) }; } return undefined; } diff --git a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts index f0c1cd641fb33..48ba4f49f33fb 100644 --- a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts @@ -18,7 +18,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; import { Schemas } from 'vs/base/common/network'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService } from 'vs/platform/label/common/label'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; @@ -39,7 +39,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil @IOpenerService openerService: IOpenerService, @INativeHostService private readonly nativeHostService: INativeHostService, @IDialogService dialogService: IDialogService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IWorkspacesService workspacesService: IWorkspacesService, @ILabelService labelService: ILabelService, @IPathService pathService: IPathService, @@ -48,7 +48,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil @ICodeEditorService codeEditorService: ICodeEditorService ) { super(hostService, contextService, historyService, environmentService, instantiationService, - configurationService, fileService, openerService, dialogService, modeService, workspacesService, labelService, pathService, commandService, editorService, codeEditorService); + configurationService, fileService, openerService, dialogService, languageService, workspacesService, labelService, pathService, commandService, editorService, codeEditorService); } private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { diff --git a/src/vs/workbench/services/dialogs/test/electron-sandbox/fileDialogService.test.ts b/src/vs/workbench/services/dialogs/test/electron-sandbox/fileDialogService.test.ts index f141c2f7d04e8..ca51958414684 100644 --- a/src/vs/workbench/services/dialogs/test/electron-sandbox/fileDialogService.test.ts +++ b/src/vs/workbench/services/dialogs/test/electron-sandbox/fileDialogService.test.ts @@ -18,7 +18,7 @@ import { FileDialogService } from 'vs/workbench/services/dialogs/electron-sandbo import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { mock } from 'vs/base/test/common/mock'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -47,7 +47,7 @@ class TestFileDialogService extends FileDialogService { @IOpenerService openerService: IOpenerService, @INativeHostService nativeHostService: INativeHostService, @IDialogService dialogService: IDialogService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IWorkspacesService workspacesService: IWorkspacesService, @ILabelService labelService: ILabelService, @IPathService pathService: IPathService, @@ -56,7 +56,7 @@ class TestFileDialogService extends FileDialogService { @ICodeEditorService codeEditorService: ICodeEditorService ) { super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, - openerService, nativeHostService, dialogService, modeService, workspacesService, labelService, pathService, commandService, editorService, codeEditorService); + openerService, nativeHostService, dialogService, languageService, workspacesService, labelService, pathService, commandService, editorService, codeEditorService); } protected override getSimpleFileDialog() { diff --git a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts index 88b80bcd2a913..ab19a7727a50a 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts @@ -14,7 +14,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { isWorkspace, IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { IJSONEditingService, IJSONValue } from 'vs/workbench/services/configuration/common/jsonEditing'; @@ -53,7 +53,7 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor @IFileService private readonly fileService: IFileService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, ) { super(); @@ -223,7 +223,7 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor label: workspaceFolder.name, description: localize('workspace folder', "Workspace Folder"), workspaceOrFolder: workspaceFolder, - iconClasses: getIconClasses(this.modelService, this.modeService, workspaceFolder.uri, FileKind.ROOT_FOLDER) + iconClasses: getIconClasses(this.modelService, this.languageService, workspaceFolder.uri, FileKind.ROOT_FOLDER) }; }); diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts index 2d6c6b77fa3ca..58c2decb98345 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts @@ -8,7 +8,7 @@ import * as json from 'vs/base/common/json'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ChordKeybinding, SimpleKeybinding } from 'vs/base/common/keybindings'; import { OS } from 'vs/base/common/platform'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; @@ -105,7 +105,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IEditorService, new TestEditorService()); instantiationService.stub(IWorkingCopyService, disposables.add(new WorkingCopyService())); instantiationService.stub(ITelemetryService, NullTelemetryService); - instantiationService.stub(IModeService, ModeServiceImpl); + instantiationService.stub(ILanguageService, ModeServiceImpl); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(ILabelService, disposables.add(instantiationService.createInstance(LabelService))); instantiationService.stub(IFilesConfigurationService, disposables.add(instantiationService.createInstance(FilesConfigurationService))); diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index 0e1ccd7a277a3..b3676c3b3c7c5 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -8,7 +8,7 @@ import { ILanguageDetectionService, ILanguageDetectionStats, LanguageDetectionSt import { FileAccess } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { URI } from 'vs/base/common/uri'; import { isWeb } from 'vs/base/common/platform'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -29,7 +29,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet constructor( @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, - @IModeService private readonly _modeService: IModeService, + @ILanguageService private readonly _languageService: ILanguageService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IModelService modelService: IModelService, @ITelemetryService telemetryService: ITelemetryService, @@ -59,7 +59,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet if (!language) { return undefined; } - return this._modeService.getModeIdByFilepathOrFirstLine(URI.file(`file.${language}`)) ?? undefined; + return this._languageService.getModeIdByFilepathOrFirstLine(URI.file(`file.${language}`)) ?? undefined; } async detectLanguage(resource: URI): Promise { diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index 3bdce479eeae5..6bad14357476e 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -8,7 +8,7 @@ import * as mime from 'vs/base/common/mime'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { ILanguageExtensionPoint, IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageExtensionPoint, ILanguageService } from 'vs/editor/common/services/languageService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -221,4 +221,4 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec return true; } -registerSingleton(IModeService, WorkbenchModeServiceImpl); +registerSingleton(ILanguageService, WorkbenchModeServiceImpl); diff --git a/src/vs/workbench/services/model/common/workbenchModelService.ts b/src/vs/workbench/services/model/common/workbenchModelService.ts index 0ce64e5254944..c33b6d50b38b8 100644 --- a/src/vs/workbench/services/model/common/workbenchModelService.ts +++ b/src/vs/workbench/services/model/common/workbenchModelService.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -24,10 +24,10 @@ export class WorkbenchModelServiceImpl extends ModelServiceImpl { @ILogService logService: ILogService, @IUndoRedoService undoRedoService: IUndoRedoService, @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IPathService private readonly _pathService: IPathService, ) { - super(configurationService, resourcePropertiesService, themeService, logService, undoRedoService, modeService, languageConfigurationService); + super(configurationService, resourcePropertiesService, themeService, logService, undoRedoService, languageService, languageConfigurationService); } protected override _schemaShouldMaintainUndoRedoElements(resource: URI) { diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 7800cc1c21fa7..7db619cdf444a 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -14,7 +14,7 @@ import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -75,7 +75,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic @IKeybindingService keybindingService: IKeybindingService, @IModelService private readonly modelService: IModelService, @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @ILabelService private readonly labelService: ILabelService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @ICommandService private readonly commandService: ICommandService, @@ -122,7 +122,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic if (this.isDefaultSettingsResource(uri)) { const target = this.getConfigurationTargetFromDefaultSettingsResource(uri); - const languageSelection = this.modeService.create('jsonc'); + const languageSelection = this.languageService.create('jsonc'); const model = this._register(this.modelService.createModel('', languageSelection, uri)); let defaultSettings: DefaultSettings | undefined; @@ -150,14 +150,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic if (this.defaultSettingsRawResource.toString() === uri.toString()) { const defaultRawSettingsEditorModel = this.instantiationService.createInstance(DefaultRawSettingsEditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); - const languageSelection = this.modeService.create('jsonc'); + const languageSelection = this.languageService.create('jsonc'); const model = this._register(this.modelService.createModel(defaultRawSettingsEditorModel.content, languageSelection, uri)); return model; } if (this.defaultKeybindingsResource.toString() === uri.toString()) { const defaultKeybindingsEditorModel = this.instantiationService.createInstance(DefaultKeybindingsEditorModel, uri); - const languageSelection = this.modeService.create('jsonc'); + const languageSelection = this.languageService.create('jsonc'); const model = this._register(this.modelService.createModel(defaultKeybindingsEditorModel.content, languageSelection, uri)); return model; } diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index 387516cc8746d..97902f3a825b7 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -16,7 +16,7 @@ import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/t import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry, StandardTokenType } from 'vs/editor/common/modes'; import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -51,7 +51,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex protected _currentTokenColorMap: string[] | null; constructor( - @IModeService protected readonly _modeService: IModeService, + @ILanguageService protected readonly _languageService: ILanguageService, @IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService, @IExtensionResourceLoaderService protected readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @INotificationService private readonly _notificationService: INotificationService, @@ -103,9 +103,9 @@ export abstract class AbstractTextMateService extends Disposable implements ITex // never hurts to be too careful continue; } - const validLanguageId = this._modeService.validateLanguageId(language); + const validLanguageId = this._languageService.validateLanguageId(language); if (validLanguageId) { - embeddedLanguages[scope] = this._modeService.languageIdCodec.encodeLanguageId(validLanguageId); + embeddedLanguages[scope] = this._languageService.languageIdCodec.encodeLanguageId(validLanguageId); } } } @@ -131,7 +131,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex let validLanguageId: string | null = null; if (grammar.language) { - validLanguageId = this._modeService.validateLanguageId(grammar.language); + validLanguageId = this._languageService.validateLanguageId(grammar.language); } this._grammarDefinitions.push({ @@ -173,7 +173,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex } TokenizationRegistry.setColorMap([null!, defaultForeground, defaultBackground]); - this._modeService.onDidEncounterLanguage((languageId) => { + this._languageService.onDidEncounterLanguage((languageId) => { this._createdModes.push(languageId); this._registerDefinitionIfAvailable(languageId); }); @@ -253,13 +253,13 @@ export abstract class AbstractTextMateService extends Disposable implements ITex } private _registerDefinitionIfAvailable(languageId: string): void { - if (!this._modeService.validateLanguageId(languageId)) { + if (!this._languageService.validateLanguageId(languageId)) { return; } if (!this._canCreateGrammarFactory()) { return; } - const encodedLanguageId = this._modeService.languageIdCodec.encodeLanguageId(languageId); + const encodedLanguageId = this._languageService.languageIdCodec.encodeLanguageId(languageId); // Here we must register the promise ASAP (without yielding!) this._tokenizersRegistrations.push(TokenizationRegistry.registerPromise(languageId, (async () => { @@ -275,7 +275,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex const tokenization = new TMTokenization(r.grammar, r.initialState, r.containsEmbeddedLanguages); tokenization.onDidEncounterLanguage((encodedLanguageId) => { if (!this._encounteredLanguages[encodedLanguageId]) { - const languageId = this._modeService.languageIdCodec.decodeLanguageId(encodedLanguageId); + const languageId = this._languageService.languageIdCodec.decodeLanguageId(encodedLanguageId); this._encounteredLanguages[encodedLanguageId] = true; this._onDidEncounterLanguage.fire(languageId); } @@ -337,7 +337,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex } private _validateGrammarExtensionPoint(extensionLocation: URI, syntax: ITMSyntaxExtensionPoint, collector: ExtensionMessageCollector): boolean { - if (syntax.language && ((typeof syntax.language !== 'string') || !this._modeService.isRegisteredMode(syntax.language))) { + if (syntax.language && ((typeof syntax.language !== 'string') || !this._languageService.isRegisteredMode(syntax.language))) { collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", grammarsExtPoint.name, String(syntax.language))); return false; } @@ -371,14 +371,14 @@ export abstract class AbstractTextMateService extends Disposable implements ITex } public async createGrammar(languageId: string): Promise { - if (!this._modeService.validateLanguageId(languageId)) { + if (!this._languageService.validateLanguageId(languageId)) { return null; } const grammarFactory = await this._getOrCreateGrammarFactory(); if (!grammarFactory.has(languageId)) { return null; } - const encodedLanguageId = this._modeService.languageIdCodec.encodeLanguageId(languageId); + const encodedLanguageId = this._languageService.languageIdCodec.encodeLanguageId(languageId); const { grammar } = await grammarFactory.createGrammar(languageId, encodedLanguageId); return grammar; } diff --git a/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts b/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts index fcdde34bfbde8..ca2d95ceb9e2f 100644 --- a/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts @@ -6,7 +6,7 @@ import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; @@ -149,7 +149,7 @@ export class TextMateService extends AbstractTextMateService { private _tokenizers: { [uri: string]: ModelWorkerTextMateTokenizer; }; constructor( - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, @INotificationService notificationService: INotificationService, @@ -159,7 +159,7 @@ export class TextMateService extends AbstractTextMateService { @IModelService private readonly _modelService: IModelService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, ) { - super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, progressService); + super(languageService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, progressService); this._worker = null; this._workerProxy = null; this._tokenizers = Object.create(null); @@ -176,7 +176,7 @@ export class TextMateService extends AbstractTextMateService { return; } const key = model.uri.toString(); - const tokenizer = new ModelWorkerTextMateTokenizer(this._workerProxy, this._modeService.languageIdCodec, model); + const tokenizer = new ModelWorkerTextMateTokenizer(this._workerProxy, this._languageService.languageIdCodec, model); this._tokenizers[key] = tokenizer; } diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index 05b6e41c71de3..51310d045fba2 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -9,7 +9,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -43,12 +43,12 @@ export class BrowserTextFileService extends AbstractTextFileService { @IPathService pathService: IPathService, @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IElevatedFileService elevatedFileService: IElevatedFileService, @ILogService logService: ILogService, @IDecorationsService decorationsService: IDecorationsService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService, uriIdentityService, modeService, logService, elevatedFileService, decorationsService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService, uriIdentityService, languageService, logService, elevatedFileService, decorationsService); this.registerListeners(); } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 232f9cf4f90c2..dc1be64fbe0eb 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -36,7 +36,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, toEncodeReadable, toDecodeStream, IDecodeStreamResult } from 'vs/workbench/services/textfile/common/encoding'; import { consumeStream, ReadableStream } from 'vs/base/common/stream'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; @@ -72,7 +72,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IPathService private readonly pathService: IPathService, @IWorkingCopyFileService private readonly workingCopyFileService: IWorkingCopyFileService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IModeService private readonly modeService: IModeService, + @ILanguageService private readonly languageService: ILanguageService, @ILogService protected readonly logService: ILogService, @IElevatedFileService private readonly elevatedFileService: IElevatedFileService, @IDecorationsService private readonly decorationsService: IDecorationsService @@ -581,19 +581,19 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } suggestFilename(mode: string, untitledName: string) { - const languageName = this.modeService.getLanguageName(mode); + const languageName = this.languageService.getLanguageName(mode); if (!languageName) { return untitledName; } - const extension = this.modeService.getExtensions(languageName)[0]; + const extension = this.languageService.getExtensions(languageName)[0]; if (extension) { if (!untitledName.endsWith(extension)) { return untitledName + extension; } } - const filename = this.modeService.getFilenames(languageName)[0]; + const filename = this.languageService.getFilenames(languageName)[0]; return filename || untitledName; } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index b24fadf363dbc..f4eab33c01d6f 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -11,7 +11,7 @@ import { IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IWorkingCopyBackupService, IResolvedWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { IFileService, FileOperationError, FileOperationResult, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED, FileSystemProviderCapabilities, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout, TaskSequentializer } from 'vs/base/common/async'; import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model'; @@ -104,7 +104,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil readonly resource: URI, private preferredEncoding: string | undefined, // encoding as chosen by the user private preferredMode: string | undefined, // mode as chosen by the user - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IModelService modelService: IModelService, @IFileService private readonly fileService: IFileService, @ITextFileService private readonly textFileService: ITextFileService, @@ -118,7 +118,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil @IPathService private readonly pathService: IPathService, @IExtensionService private readonly extensionService: IExtensionService, ) { - super(modelService, modeService, languageDetectionService, accessibilityService); + super(modelService, languageService, languageDetectionService, accessibilityService); // Make known to working copy service this._register(this.workingCopyService.registerWorkingCopy(this)); @@ -189,7 +189,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } const firstLineText = this.getFirstLineText(this.textEditorModel); - const languageSelection = this.getOrCreateMode(this.resource, this.modeService, this.preferredMode, firstLineText); + const languageSelection = this.getOrCreateMode(this.resource, this.languageService, this.preferredMode, firstLineText); this.modelService.setMode(this.textEditorModel, languageSelection); } diff --git a/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts index 7e205ceaf046a..840c0be351e88 100644 --- a/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts @@ -22,7 +22,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; import { ILogService } from 'vs/platform/log/common/log'; import { Promises } from 'vs/base/common/async'; @@ -48,12 +48,12 @@ export class NativeTextFileService extends AbstractTextFileService { @IPathService pathService: IPathService, @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IElevatedFileService elevatedFileService: IElevatedFileService, @ILogService logService: ILogService, @IDecorationsService decorationsService: IDecorationsService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService, uriIdentityService, modeService, logService, elevatedFileService, decorationsService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService, uriIdentityService, languageService, logService, elevatedFileService, decorationsService); this.environmentService = environmentService; diff --git a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts index 992fd78eee686..2fc90f25c38a1 100644 --- a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts @@ -44,7 +44,7 @@ suite('Workbench - TextModelResolverService', () => { provideTextContent: async function (resource: URI): Promise { if (resource.scheme === 'test') { let modelContent = 'Hello Test'; - let languageSelection = accessor.modeService.create('json'); + let languageSelection = accessor.languageService.create('json'); return accessor.modelService.createModel(modelContent, languageSelection, resource); } @@ -179,7 +179,7 @@ suite('Workbench - TextModelResolverService', () => { await waitForIt; let modelContent = 'Hello Test'; - let languageSelection = accessor.modeService.create('json'); + let languageSelection = accessor.languageService.create('json'); return accessor.modelService.createModel(modelContent, languageSelection, resource); } }); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 8ebf4f071b0cb..7c9f2b1e4b6cb 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -6,7 +6,7 @@ import { ISaveOptions } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { URI } from 'vs/base/common/uri'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; @@ -129,7 +129,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private readonly initialValue: string | undefined, private preferredMode: string | undefined, private preferredEncoding: string | undefined, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IModelService modelService: IModelService, @IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService, @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, @@ -140,7 +140,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt @ILanguageDetectionService languageDetectionService: ILanguageDetectionService, @IAccessibilityService accessibilityService: IAccessibilityService, ) { - super(modelService, modeService, languageDetectionService, accessibilityService); + super(modelService, languageService, languageDetectionService, accessibilityService); // Make known to working copy service this._register(this.workingCopyService.registerWorkingCopy(this)); diff --git a/src/vs/workbench/test/browser/codeeditor.test.ts b/src/vs/workbench/test/browser/codeeditor.test.ts index 399f2af607145..250b45ad31898 100644 --- a/src/vs/workbench/test/browser/codeeditor.test.ts +++ b/src/vs/workbench/test/browser/codeeditor.test.ts @@ -8,7 +8,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { URI } from 'vs/base/common/uri'; import { workbenchInstantiationService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { RangeHighlightDecorations } from 'vs/workbench/browser/codeeditor'; import { TextModel } from 'vs/editor/common/model/textModel'; @@ -40,7 +40,7 @@ suite('Editor - Range decorations', () => { disposables = new DisposableStore(); instantiationService = workbenchInstantiationService(undefined, disposables); instantiationService.stub(IEditorService, new TestEditorService()); - instantiationService.stub(IModeService, ModeServiceImpl); + instantiationService.stub(ILanguageService, ModeServiceImpl); instantiationService.stub(IModelService, stubModelService(instantiationService)); text = 'LINE1' + '\n' + 'LINE2' + '\n' + 'LINE3' + '\n' + 'LINE4' + '\r\n' + 'LINE5'; model = aModel(URI.file('some_file')); diff --git a/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts index 7c11f4b91bc3a..063fd4f548261 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts @@ -34,7 +34,7 @@ suite('TextDiffEditorModel', () => { provideTextContent: async function (resource: URI): Promise { if (resource.scheme === 'test') { let modelContent = 'Hello Test'; - let languageSelection = accessor.modeService.create('json'); + let languageSelection = accessor.languageService.create('json'); return accessor.modelService.createModel(modelContent, languageSelection, resource); } diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index b73308c4ef093..69bcc4f88b600 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -63,11 +63,11 @@ suite('EditorModel', () => { } let instantiationService: TestInstantiationService; - let modeService: IModeService; + let languageService: ILanguageService; setup(() => { instantiationService = new TestInstantiationService(); - modeService = instantiationService.stub(IModeService, ModeServiceImpl); + languageService = instantiationService.stub(ILanguageService, ModeServiceImpl); }); test('basics', async () => { @@ -91,7 +91,7 @@ suite('EditorModel', () => { test('BaseTextEditorModel', async () => { let modelService = stubModelService(instantiationService); - const model = new MyTextEditorModel(modelService, modeService, instantiationService.createInstance(LanguageDetectionService), instantiationService.createInstance(TestAccessibilityService)); + const model = new MyTextEditorModel(modelService, languageService, instantiationService.createInstance(LanguageDetectionService), instantiationService.createInstance(TestAccessibilityService)); await model.resolve(); model.createTextEditorModel(createTextBufferFactory('foo'), null!, Mimes.text); diff --git a/src/vs/workbench/test/browser/parts/editor/textResourceEditorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/textResourceEditorInput.test.ts index af697a42ead04..1bc501716b67a 100644 --- a/src/vs/workbench/test/browser/parts/editor/textResourceEditorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/textResourceEditorInput.test.ts @@ -31,7 +31,7 @@ suite('TextResourceEditorInput', () => { test('basics', async () => { const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', undefined, undefined); @@ -47,7 +47,7 @@ suite('TextResourceEditorInput', () => { }); const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', 'resource-input-test', undefined); @@ -68,7 +68,7 @@ suite('TextResourceEditorInput', () => { }); const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', undefined, undefined); input.setPreferredMode('resource-input-test'); @@ -80,7 +80,7 @@ suite('TextResourceEditorInput', () => { test('preferred contents (via ctor)', async () => { const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', undefined, 'My Resource Input Contents'); @@ -97,7 +97,7 @@ suite('TextResourceEditorInput', () => { test('preferred contents (via setPreferredContents)', async () => { const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', undefined, undefined); input.setPreferredContents('My Resource Input Contents'); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 47b57406ce67b..8b3f887601d84 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -28,7 +28,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IResourceEncoding, ITextFileService, IReadTextFileOptions, ITextFileStreamContent, IWriteTextFileOptions, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -230,7 +230,7 @@ export function workbenchInstantiationService( const accessibilityService = new TestAccessibilityService(); instantiationService.stub(IAccessibilityService, accessibilityService); instantiationService.stub(IFileDialogService, instantiationService.createInstance(TestFileDialogService)); - instantiationService.stub(IModeService, disposables.add(instantiationService.createInstance(ModeServiceImpl))); + instantiationService.stub(ILanguageService, disposables.add(instantiationService.createInstance(ModeServiceImpl))); instantiationService.stub(IHistoryService, new TestHistoryService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); @@ -294,7 +294,7 @@ export class TestServiceAccessor { @IPathService public pathService: IPathService, @IEditorGroupsService public editorGroupService: IEditorGroupsService, @IEditorResolverService public editorResolverService: IEditorResolverService, - @IModeService public modeService: IModeService, + @ILanguageService public languageService: ILanguageService, @ITextModelService public textModelResolverService: ITextModelService, @IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService, @IConfigurationService public testConfigurationService: TestConfigurationService, @@ -335,7 +335,7 @@ export class TestTextFileService extends BrowserTextFileService { @IPathService pathService: IPathService, @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @ILogService logService: ILogService, @IElevatedFileService elevatedFileService: IElevatedFileService, @IDecorationsService decorationsService: IDecorationsService @@ -356,7 +356,7 @@ export class TestTextFileService extends BrowserTextFileService { pathService, workingCopyFileService, uriIdentityService, - modeService, + languageService, elevatedFileService, logService, decorationsService diff --git a/src/vs/workbench/test/electron-browser/testing.ts b/src/vs/workbench/test/electron-browser/testing.ts index 668632bd3af5e..bf7bdd1ec3faa 100644 --- a/src/vs/workbench/test/electron-browser/testing.ts +++ b/src/vs/workbench/test/electron-browser/testing.ts @@ -14,6 +14,6 @@ import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; export function assertCleanState(): void { // If this test fails, it is a clear indication that // your test or suite is leaking services (e.g. via leaking text models) - // assert.strictEqual(ModeServiceImpl.instanceCount, 0, 'No leaking IModeService'); + // assert.strictEqual(ModeServiceImpl.instanceCount, 0, 'No leaking ILanguageService'); assert.strictEqual(LanguagesRegistry.instanceCount, 0, 'No leaking LanguagesRegistry'); } diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 5ea6d21bcd81e..9e82ea5c6cccb 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -37,7 +37,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { ILanguageService } from 'vs/editor/common/services/languageService'; import { IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; import { homedir, release, tmpdir, hostname } from 'os'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -91,7 +91,7 @@ export class TestTextFileService extends NativeTextFileService { @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @ILogService logService: ILogService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @IModeService modeService: IModeService, + @ILanguageService languageService: ILanguageService, @IElevatedFileService elevatedFileService: IElevatedFileService, @IDecorationsService decorationsService: IDecorationsService ) { @@ -111,7 +111,7 @@ export class TestTextFileService extends NativeTextFileService { pathService, workingCopyFileService, uriIdentityService, - modeService, + languageService, elevatedFileService, logService, decorationsService From fec4856aff7dcdbe240519db1975412a2e5134d2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 14:43:31 +0100 Subject: [PATCH 0425/2210] Renames `ModeService` to `LanguageService` --- .../{modeServiceImpl.ts => languageServiceImpl.ts} | 6 +++--- .../editor/standalone/browser/standaloneServices.ts | 4 ++-- .../test/browser/standaloneLanguages.test.ts | 4 ++-- .../editor/standalone/test/monarch/monarch.test.ts | 12 ++++++------ src/vs/editor/test/browser/testCodeEditor.ts | 4 ++-- src/vs/editor/test/common/editorTestUtils.ts | 4 ++-- .../editor/test/common/services/modelService.test.ts | 8 ++++---- .../codeActions/common/codeActionsExtensionPoint.ts | 2 +- .../common/documentationExtensionPoint.ts | 2 +- .../contrib/customEditor/common/extensionPoint.ts | 2 +- .../notebook/test/browser/testNotebookEditor.ts | 4 ++-- .../contrib/snippets/browser/snippetsService.ts | 2 +- .../snippets/test/browser/snippetsService.test.ts | 6 +++--- .../test/browser/keybindingEditing.test.ts | 4 ++-- ...nchModeService.ts => workbenchLanguageService.ts} | 6 +++--- .../workbench/services/textMate/common/TMGrammars.ts | 2 +- .../api/mainThreadDocumentsAndEditors.test.ts | 4 ++-- .../test/browser/api/mainThreadEditors.test.ts | 4 ++-- src/vs/workbench/test/browser/codeeditor.test.ts | 4 ++-- .../test/browser/parts/editor/editorModel.test.ts | 4 ++-- .../workbench/test/browser/workbenchTestServices.ts | 4 ++-- src/vs/workbench/test/electron-browser/testing.ts | 4 ++-- .../textsearch.perf.integrationTest.ts | 4 ++-- src/vs/workbench/workbench.common.main.ts | 2 +- 24 files changed, 51 insertions(+), 51 deletions(-) rename src/vs/editor/common/services/{modeServiceImpl.ts => languageServiceImpl.ts} (97%) rename src/vs/workbench/services/mode/common/{workbenchModeService.ts => workbenchLanguageService.ts} (97%) diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/languageServiceImpl.ts similarity index 97% rename from src/vs/editor/common/services/modeServiceImpl.ts rename to src/vs/editor/common/services/languageServiceImpl.ts index a9298b20b1243..f8074989124c0 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/languageServiceImpl.ts @@ -47,7 +47,7 @@ class LanguageSelection implements ILanguageSelection { } } -export class ModeServiceImpl extends Disposable implements ILanguageService { +export class LanguageService extends Disposable implements ILanguageService { public _serviceBrand: undefined; static instanceCount = 0; @@ -64,7 +64,7 @@ export class ModeServiceImpl extends Disposable implements ILanguageService { constructor(warnOnOverwrite = false) { super(); - ModeServiceImpl.instanceCount++; + LanguageService.instanceCount++; this._encounteredLanguages = new Set(); this._registry = this._register(new LanguagesRegistry(true, warnOnOverwrite)); this.languageIdCodec = this._registry.languageIdCodec; @@ -72,7 +72,7 @@ export class ModeServiceImpl extends Disposable implements ILanguageService { } public override dispose(): void { - ModeServiceImpl.instanceCount--; + LanguageService.instanceCount--; super.dispose(); } diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index d31081f80fceb..e449c4441d3fd 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -9,7 +9,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; import { ILanguageService } from 'vs/editor/common/services/languageService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -150,7 +150,7 @@ export module StaticServices { export const markerService = define(IMarkerService, () => new MarkerService()); - export const languageService = define(ILanguageService, (o) => new ModeServiceImpl()); + export const languageService = define(ILanguageService, (o) => new LanguageService()); export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 8502a330bf762..1a26f74be65b3 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -11,7 +11,7 @@ import { Token } from 'vs/editor/common/core/token'; import { IState, LanguageId, MetadataConsts } from 'vs/editor/common/modes'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { ILineTokens, IToken, TokenizationSupport2Adapter, TokensProvider } from 'vs/editor/standalone/browser/standaloneLanguages'; import { IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; @@ -113,7 +113,7 @@ suite('TokenizationSupport2Adapter', () => { } const disposables = new DisposableStore(); - const languageService = disposables.add(new ModeServiceImpl()); + const languageService = disposables.add(new LanguageService()); disposables.add(ModesRegistry.registerLanguage({ id: languageId })); const adapter = new TokenizationSupport2Adapter( languageId, diff --git a/src/vs/editor/standalone/test/monarch/monarch.test.ts b/src/vs/editor/standalone/test/monarch/monarch.test.ts index e0eacc902188e..6ecf23a0d0c48 100644 --- a/src/vs/editor/standalone/test/monarch/monarch.test.ts +++ b/src/vs/editor/standalone/test/monarch/monarch.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { ILanguageService } from 'vs/editor/common/services/languageService'; import { MonarchTokenizer } from 'vs/editor/standalone/common/monarch/monarchLexer'; import { compile } from 'vs/editor/standalone/common/monarch/monarchCompile'; @@ -31,7 +31,7 @@ suite('Monarch', () => { } test('Ensure @rematch and nextEmbedded can be used together in Monarch grammar', () => { - const languageService = new ModeServiceImpl(); + const languageService = new LanguageService(); const innerModeRegistration = ModesRegistry.registerLanguage({ id: 'sql' }); @@ -110,7 +110,7 @@ suite('Monarch', () => { }); test('microsoft/monaco-editor#1235: Empty Line Handling', () => { - const languageService = new ModeServiceImpl(); + const languageService = new LanguageService(); const tokenizer = createMonarchTokenizer(languageService, 'test', { tokenizer: { root: [ @@ -167,7 +167,7 @@ suite('Monarch', () => { }); test('microsoft/monaco-editor#2265: Exit a state at end of line', () => { - const languageService = new ModeServiceImpl(); + const languageService = new LanguageService(); const tokenizer = createMonarchTokenizer(languageService, 'test', { includeLF: true, tokenizer: { @@ -215,7 +215,7 @@ suite('Monarch', () => { }); test('issue #115662: monarchCompile function need an extra option which can control replacement', () => { - const languageService = new ModeServiceImpl(); + const languageService = new LanguageService(); const tokenizer1 = createMonarchTokenizer(languageService, 'test', { ignoreCase: false, @@ -269,7 +269,7 @@ suite('Monarch', () => { }); test('microsoft/monaco-editor#2424: Allow to target @@', () => { - const languageService = new ModeServiceImpl(); + const languageService = new LanguageService(); const tokenizer = createMonarchTokenizer(languageService, 'test', { ignoreCase: false, diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 4a6bfe28ca0f3..17e701664b69b 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -16,7 +16,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageCo import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ILanguageService } from 'vs/editor/common/services/languageService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestCodeEditorService, TestCommandService } from 'vs/editor/test/browser/editorTestServices'; @@ -182,7 +182,7 @@ export function createCodeEditorServices(disposables: DisposableStore, services: define(INotificationService, TestNotificationService); define(IDialogService, TestDialogService); define(IUndoRedoService, UndoRedoService); - define(ILanguageService, ModeServiceImpl); + define(ILanguageService, LanguageService); define(ILanguageConfigurationService, TestLanguageConfigurationService); define(IConfigurationService, TestConfigurationService); define(ITextResourcePropertiesService, TestTextResourcePropertiesService); diff --git a/src/vs/editor/test/common/editorTestUtils.ts b/src/vs/editor/test/common/editorTestUtils.ts index 8695311077ded..0beec17e0abee 100644 --- a/src/vs/editor/test/common/editorTestUtils.ts +++ b/src/vs/editor/test/common/editorTestUtils.ts @@ -9,7 +9,7 @@ import { BracketPairColorizationOptions, DefaultEndOfLine, ITextBufferFactory, I import { TextModel } from 'vs/editor/common/model/textModel'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ILanguageService } from 'vs/editor/common/services/languageService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -94,7 +94,7 @@ export function createModelServices(disposables: DisposableStore, services: Serv define(INotificationService, TestNotificationService); define(IDialogService, TestDialogService); define(IUndoRedoService, UndoRedoService); - define(ILanguageService, ModeServiceImpl); + define(ILanguageService, LanguageService); define(ILanguageConfigurationService, TestLanguageConfigurationService); define(IConfigurationService, TestConfigurationService); define(ITextResourcePropertiesService, TestTextResourcePropertiesService); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 6f4d10db8b278..40efc85b13db3 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -25,7 +25,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { DocumentSemanticTokensProvider, DocumentSemanticTokensProviderRegistry, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/modes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Barrier, timeout } from 'vs/base/common/async'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -53,7 +53,7 @@ suite('ModelService', () => { new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), - disposables.add(new ModeServiceImpl()), + disposables.add(new LanguageService()), new TestLanguageConfigurationService() )); }); @@ -425,10 +425,10 @@ suite('ModelSemanticColoring', () => { themeService, new NullLogService(), new UndoRedoService(new TestDialogService(), new TestNotificationService()), - disposables.add(new ModeServiceImpl()), + disposables.add(new LanguageService()), new TestLanguageConfigurationService() )); - languageService = disposables.add(new ModeServiceImpl(false)); + languageService = disposables.add(new LanguageService(false)); }); teardown(() => { diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts index cb89084f81978..11f882ec219a8 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; +import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchLanguageService'; export enum CodeActionExtensionPointFields { languages = 'languages', diff --git a/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts index bb848f8d64d17..b71df3c555e96 100644 --- a/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; +import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchLanguageService'; export enum DocumentationExtensionPointFields { when = 'when', diff --git a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts index 279da3d2282a7..86d4f76c461ee 100644 --- a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts @@ -7,7 +7,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as nls from 'vs/nls'; import { CustomEditorPriority, CustomEditorSelector } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; +import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchLanguageService'; namespace Fields { export const viewType = 'viewType'; diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index b8f0b300d0344..8833d86e48be5 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -18,7 +18,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageCo import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ILanguageService } from 'vs/editor/common/services/languageService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; @@ -158,7 +158,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi export function setupInstantiationService(disposables = new DisposableStore()) { const instantiationService = new TestInstantiationService(); - instantiationService.stub(ILanguageService, disposables.add(new ModeServiceImpl())); + instantiationService.stub(ILanguageService, disposables.add(new LanguageService())); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 334ac214861b7..56888c34fb407 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -21,7 +21,7 @@ import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/comm import { ISnippetGetOptions, ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; import { Snippet, SnippetFile, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; +import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchLanguageService'; import { SnippetCompletionProvider } from './snippetCompletionProvider'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { ResourceMap } from 'vs/base/common/map'; diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 6111bbb112430..f0982a2e5ca44 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider'; import { Position } from 'vs/editor/common/core/position'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; @@ -52,12 +52,12 @@ suite('SnippetsService', function () { }); let disposables: DisposableStore; - let languageService: ModeServiceImpl; + let languageService: LanguageService; let snippetService: ISnippetsService; setup(function () { disposables = new DisposableStore(); - languageService = disposables.add(new ModeServiceImpl()); + languageService = disposables.add(new LanguageService()); snippetService = new SimpleSnippetService([new Snippet( ['fooLang'], 'barTest', diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts index 58c2decb98345..eac2f6fc30126 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts @@ -9,7 +9,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ChordKeybinding, SimpleKeybinding } from 'vs/base/common/keybindings'; import { OS } from 'vs/base/common/platform'; import { ILanguageService } from 'vs/editor/common/services/languageService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -105,7 +105,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IEditorService, new TestEditorService()); instantiationService.stub(IWorkingCopyService, disposables.add(new WorkingCopyService())); instantiationService.stub(ITelemetryService, NullTelemetryService); - instantiationService.stub(ILanguageService, ModeServiceImpl); + instantiationService.stub(ILanguageService, LanguageService); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(ILabelService, disposables.add(instantiationService.createInstance(LabelService))); instantiationService.stub(IFilesConfigurationService, disposables.add(instantiationService.createInstance(FilesConfigurationService))); diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchLanguageService.ts similarity index 97% rename from src/vs/workbench/services/mode/common/workbenchModeService.ts rename to src/vs/workbench/services/mode/common/workbenchLanguageService.ts index 6bad14357476e..d647fad25cc5b 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchLanguageService.ts @@ -9,7 +9,7 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { ILanguageExtensionPoint, ILanguageService } from 'vs/editor/common/services/languageService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FILES_ASSOCIATIONS_CONFIG, IFilesConfiguration } from 'vs/platform/files/common/files'; @@ -91,7 +91,7 @@ export const languagesExtPoint: IExtensionPoint = } }); -export class WorkbenchModeServiceImpl extends ModeServiceImpl { +export class WorkbenchLanguageService extends LanguageService { private _configurationService: IConfigurationService; private _extensionService: IExtensionService; @@ -221,4 +221,4 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec return true; } -registerSingleton(ILanguageService, WorkbenchModeServiceImpl); +registerSingleton(ILanguageService, WorkbenchLanguageService); diff --git a/src/vs/workbench/services/textMate/common/TMGrammars.ts b/src/vs/workbench/services/textMate/common/TMGrammars.ts index 6cc44b7ac1216..e948093b34349 100644 --- a/src/vs/workbench/services/textMate/common/TMGrammars.ts +++ b/src/vs/workbench/services/textMate/common/TMGrammars.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { ExtensionsRegistry, IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; +import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchLanguageService'; export interface IEmbeddedLanguagesMap { [scopeName: string]: string; diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index e1264365e99af..c086cdcd9d794 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts @@ -30,7 +30,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { DisposableStore } from 'vs/base/common/lifecycle'; suite('MainThreadDocumentsAndEditors', () => { @@ -67,7 +67,7 @@ suite('MainThreadDocumentsAndEditors', () => { new TestThemeService(), new NullLogService(), undoRedoService, - disposables.add(new ModeServiceImpl()), + disposables.add(new LanguageService()), new TestLanguageConfigurationService() ); codeEditorService = new TestCodeEditorService(); diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index fb2c14e6a1da3..5d6bc0b0e7109 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -54,7 +54,7 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; suite('MainThreadEditors', () => { @@ -88,7 +88,7 @@ suite('MainThreadEditors', () => { new TestThemeService(), new NullLogService(), undoRedoService, - disposables.add(new ModeServiceImpl()), + disposables.add(new LanguageService()), new TestLanguageConfigurationService() ); diff --git a/src/vs/workbench/test/browser/codeeditor.test.ts b/src/vs/workbench/test/browser/codeeditor.test.ts index 250b45ad31898..b69c9ac5a2748 100644 --- a/src/vs/workbench/test/browser/codeeditor.test.ts +++ b/src/vs/workbench/test/browser/codeeditor.test.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { workbenchInstantiationService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILanguageService } from 'vs/editor/common/services/languageService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { RangeHighlightDecorations } from 'vs/workbench/browser/codeeditor'; import { TextModel } from 'vs/editor/common/model/textModel'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; @@ -40,7 +40,7 @@ suite('Editor - Range decorations', () => { disposables = new DisposableStore(); instantiationService = workbenchInstantiationService(undefined, disposables); instantiationService.stub(IEditorService, new TestEditorService()); - instantiationService.stub(ILanguageService, ModeServiceImpl); + instantiationService.stub(ILanguageService, LanguageService); instantiationService.stub(IModelService, stubModelService(instantiationService)); text = 'LINE1' + '\n' + 'LINE2' + '\n' + 'LINE3' + '\n' + 'LINE4' + '\r\n' + 'LINE5'; model = aModel(URI.file('some_file')); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index 69bcc4f88b600..69ff44394142c 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -8,7 +8,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILanguageService } from 'vs/editor/common/services/languageService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; @@ -67,7 +67,7 @@ suite('EditorModel', () => { setup(() => { instantiationService = new TestInstantiationService(); - languageService = instantiationService.stub(ILanguageService, ModeServiceImpl); + languageService = instantiationService.stub(ILanguageService, LanguageService); }); test('basics', async () => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 8b3f887601d84..968faa34d34af 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -25,7 +25,7 @@ import { ILifecycleService, ShutdownReason, StartupKind, LifecyclePhase, WillShu import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { FileOperationEvent, IFileService, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, ICreateFileOptions, IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, IFileStatWithMetadata, IResolveMetadataFileOptions, IWriteFileOptions, IReadFileOptions, IFileContent, IFileStreamContent, FileOperationError, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, IReadFileStreamOptions, IFileSystemProviderCapabilitiesChangeEvent, IRawFileChangesEvent } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IResourceEncoding, ITextFileService, IReadTextFileOptions, ITextFileStreamContent, IWriteTextFileOptions, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { ILanguageService } from 'vs/editor/common/services/languageService'; @@ -230,7 +230,7 @@ export function workbenchInstantiationService( const accessibilityService = new TestAccessibilityService(); instantiationService.stub(IAccessibilityService, accessibilityService); instantiationService.stub(IFileDialogService, instantiationService.createInstance(TestFileDialogService)); - instantiationService.stub(ILanguageService, disposables.add(instantiationService.createInstance(ModeServiceImpl))); + instantiationService.stub(ILanguageService, disposables.add(instantiationService.createInstance(LanguageService))); instantiationService.stub(IHistoryService, new TestHistoryService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); diff --git a/src/vs/workbench/test/electron-browser/testing.ts b/src/vs/workbench/test/electron-browser/testing.ts index bf7bdd1ec3faa..1f55ae48629e0 100644 --- a/src/vs/workbench/test/electron-browser/testing.ts +++ b/src/vs/workbench/test/electron-browser/testing.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; -// import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +// import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; /** * This function is called before test running and also again at the end of test running @@ -14,6 +14,6 @@ import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; export function assertCleanState(): void { // If this test fails, it is a clear indication that // your test or suite is leaking services (e.g. via leaking text models) - // assert.strictEqual(ModeServiceImpl.instanceCount, 0, 'No leaking ILanguageService'); + // assert.strictEqual(LanguageService.instanceCount, 0, 'No leaking ILanguageService'); assert.strictEqual(LanguagesRegistry.instanceCount, 0, 'No leaking LanguagesRegistry'); } diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index bd7803e91f33f..2a59d31db706d 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -11,7 +11,7 @@ import * as path from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -86,7 +86,7 @@ suite.skip('TextSearch performance (integration)', () => { new TestThemeService(), logService, undoRedoService, - new ModeServiceImpl(), + new LanguageService(), new TestLanguageConfigurationService() ), ], diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 0cd14bca01755..e8568dd63aa81 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -69,7 +69,7 @@ import 'vs/workbench/services/keybinding/browser/keybindingService'; import 'vs/workbench/services/untitled/common/untitledTextEditorService'; import 'vs/workbench/services/textresourceProperties/common/textResourcePropertiesService'; import 'vs/workbench/services/textfile/common/textEditorService'; -import 'vs/workbench/services/mode/common/workbenchModeService'; +import 'vs/workbench/services/mode/common/workbenchLanguageService'; import 'vs/workbench/services/model/common/workbenchModelService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService'; From 0c202aa22a8a6eceadcf519351ab37cdd4fd17a7 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 8 Dec 2021 14:48:04 +0100 Subject: [PATCH 0426/2210] Improve multiple lock file warning Fixes #138174 --- extensions/npm/src/tasks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 911d66fbe3bd3..1fb4603e48e74 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -136,7 +136,7 @@ export async function getPackageManager(extensionContext: ExtensionContext, fold packageManagerName = name; const neverShowWarning = 'npm.multiplePMWarning.neverShow'; if (showWarning && multiplePMDetected && !extensionContext.globalState.get(neverShowWarning)) { - const multiplePMWarning = localize('npm.multiplePMWarning', 'Using {0} as the preferred package manager. Found multiple lockfiles for {1}.', packageManagerName, folder.fsPath); + const multiplePMWarning = localize('npm.multiplePMWarning', 'Using {0} as the preferred package manager. Found multiple lockfiles for {1}. To resolve this issue, delete the lockfiles that don\'t match your preferred package manager or change the setting "npm.packageManager" to a value other than "auto".', packageManagerName, folder.fsPath); const neverShowAgain = localize('npm.multiplePMWarning.doNotShow', "Do not show again"); const learnMore = localize('npm.multiplePMWarning.learnMore', "Learn more"); window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => { From fcf55dce4a99962e667cb083a4eedf60f255ddd2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 14:53:01 +0100 Subject: [PATCH 0427/2210] Split `isRegisteredMode` into `isRegisteredLanguageId` and `isRegisteredMimeType` --- src/vs/editor/common/services/languageService.ts | 3 ++- .../editor/common/services/languageServiceImpl.ts | 8 ++++++-- src/vs/editor/common/services/languagesRegistry.ts | 13 ++++++------- .../standalone/common/monarch/monarchLexer.ts | 7 ++++++- .../contrib/snippets/browser/snippetsService.ts | 2 +- .../textMate/browser/abstractTextMateService.ts | 2 +- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/common/services/languageService.ts b/src/vs/editor/common/services/languageService.ts index a1fedffc7a286..96f64e7f74e14 100644 --- a/src/vs/editor/common/services/languageService.ts +++ b/src/vs/editor/common/services/languageService.ts @@ -35,7 +35,8 @@ export interface ILanguageService { onLanguagesMaybeChanged: Event; // --- reading - isRegisteredMode(mimetypeOrModeId: string): boolean; + isRegisteredLanguageId(languageId: string): boolean; + isRegisteredMimeType(mimeType: string): boolean; getRegisteredModes(): string[]; getRegisteredLanguageNames(): string[]; getExtensions(alias: string): string[]; diff --git a/src/vs/editor/common/services/languageServiceImpl.ts b/src/vs/editor/common/services/languageServiceImpl.ts index f8074989124c0..1ad8f8205a0ce 100644 --- a/src/vs/editor/common/services/languageServiceImpl.ts +++ b/src/vs/editor/common/services/languageServiceImpl.ts @@ -76,8 +76,12 @@ export class LanguageService extends Disposable implements ILanguageService { super.dispose(); } - public isRegisteredMode(mimetypeOrModeId: string): boolean { - return this._registry.isRegisteredMode(mimetypeOrModeId); + public isRegisteredLanguageId(languageId: string): boolean { + return this._registry.isRegisteredLanguageId(languageId); + } + + public isRegisteredMimeType(mimeType: string): boolean { + return this._registry.isRegisteredMimeType(mimeType); } public getRegisteredModes(): string[] { diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index eb3297c614c89..51897867f2010 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -257,13 +257,12 @@ export class LanguagesRegistry extends Disposable { } } - public isRegisteredMode(mimetypeOrModeId: string): boolean { - // Is this a known mime type ? - if (hasOwnProperty.call(this._mimeTypesMap, mimetypeOrModeId)) { - return true; - } - // Is this a known mode id ? - return hasOwnProperty.call(this._languages, mimetypeOrModeId); + public isRegisteredLanguageId(languageId: string): boolean { + return hasOwnProperty.call(this._languages, languageId); + } + + public isRegisteredMimeType(mimeType: string): boolean { + return hasOwnProperty.call(this._mimeTypesMap, mimeType); } public getRegisteredModes(): string[] { diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index 1ce3050ba9386..ed5c6ef154fa6 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -859,7 +859,12 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } private _locateMode(mimetypeOrModeId: string): string | null { - if (!mimetypeOrModeId || !this._languageService.isRegisteredMode(mimetypeOrModeId)) { + if (!mimetypeOrModeId) { + return null; + } + const isRegisteredLanguageId = this._languageService.isRegisteredLanguageId(mimetypeOrModeId); + const isRegisteredMimeType = this._languageService.isRegisteredMimeType(mimetypeOrModeId); + if (!isRegisteredLanguageId && !isRegisteredMimeType) { return null; } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 56888c34fb407..ae2d95c2b225e 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -63,7 +63,7 @@ namespace snippetExt { return null; } - if (!isFalsyOrWhitespace(snippet.language) && !languageService.isRegisteredMode(snippet.language)) { + if (!isFalsyOrWhitespace(snippet.language) && !languageService.isRegisteredLanguageId(snippet.language)) { extension.collector.error(localize( 'invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index 97902f3a825b7..f42d8e52a5941 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -337,7 +337,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex } private _validateGrammarExtensionPoint(extensionLocation: URI, syntax: ITMSyntaxExtensionPoint, collector: ExtensionMessageCollector): boolean { - if (syntax.language && ((typeof syntax.language !== 'string') || !this._languageService.isRegisteredMode(syntax.language))) { + if (syntax.language && ((typeof syntax.language !== 'string') || !this._languageService.isRegisteredLanguageId(syntax.language))) { collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", grammarsExtPoint.name, String(syntax.language))); return false; } From 3ea9cd4bda3b8f9f2d66c7e07e60456d6ac5e539 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Dec 2021 05:56:31 -0800 Subject: [PATCH 0428/2210] Await process exit from remote on terminal kill/kill all commands Part of #138049 --- .../workbench/contrib/terminal/browser/terminalActions.ts | 8 ++++++-- .../workbench/contrib/terminal/browser/terminalService.ts | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 6650805fff3bd..eb06772529146 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1804,9 +1804,11 @@ export function registerTerminalActions() { } async run(accessor: ServicesAccessor) { const terminalService = accessor.get(ITerminalService); + const disposePromises: Promise[] = []; for (const instance of terminalService.instances) { - await terminalService.safeDisposeTerminal(instance); + disposePromises.push(terminalService.safeDisposeTerminal(instance)); } + await Promise.all(disposePromises); } }); registerAction2(class extends Action2 { @@ -1856,9 +1858,11 @@ export function registerTerminalActions() { return; } const terminalService = accessor.get(ITerminalService); + const disposePromises: Promise[] = []; for (const instance of selectedInstances) { - terminalService.safeDisposeTerminal(instance); + disposePromises.push(terminalService.safeDisposeTerminal(instance)); } + await Promise.all(disposePromises); if (terminalService.instances.length > 0) { accessor.get(ITerminalGroupService).focusTabs(); focusNext(accessor); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 13ef04d394111..9610f47fd53f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -342,7 +342,10 @@ export class TerminalService implements ITerminalService { return; } } - instance.dispose(); + return new Promise(r => { + instance.onExit(() => r()); + instance.dispose(); + }); } private _setConnected() { From c71424a61de500cda911c0948e2e6c3140b2b3e6 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 15:05:45 +0100 Subject: [PATCH 0429/2210] Rename some `ILanguageService` methods --- .../editor/browser/core/markdownRenderer.ts | 2 +- .../editor/common/services/languageService.ts | 6 ++-- .../common/services/languageServiceImpl.ts | 12 ++++---- .../common/services/languagesRegistry.ts | 6 ++-- .../standalone/common/monarch/monarchLexer.ts | 6 ++-- .../common/services/languagesRegistry.test.ts | 30 +++++++++---------- .../api/browser/mainThreadLanguages.ts | 4 +-- .../api/browser/mainThreadNotebookKernels.ts | 4 +-- .../browser/parts/editor/editorStatus.ts | 8 ++--- .../browser/markdownDocumentRenderer.ts | 2 +- .../cellStatusBar/statusBarProviders.ts | 2 +- .../contrib/codeRenderer/codeRenderer.ts | 2 +- .../browser/controller/cellOperations.ts | 2 +- .../browser/controller/editActions.ts | 2 +- .../notebook/browser/notebook.contribution.ts | 2 +- .../view/renderers/backLayerWebView.ts | 2 +- .../common/model/notebookCellTextModel.ts | 10 +++---- .../preferences/browser/preferencesActions.ts | 4 +-- .../snippets/browser/configureSnippets.ts | 10 +++---- .../common/walkThroughContentProvider.ts | 2 +- .../mode/common/workbenchLanguageService.ts | 4 +-- 21 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/vs/editor/browser/core/markdownRenderer.ts b/src/vs/editor/browser/core/markdownRenderer.ts index f89e75f64b3ce..c1b230c676326 100644 --- a/src/vs/editor/browser/core/markdownRenderer.ts +++ b/src/vs/editor/browser/core/markdownRenderer.ts @@ -75,7 +75,7 @@ export class MarkdownRenderer { // it is possible no alias is given in which case we fall back to the current editor lang let languageId: string | undefined | null; if (languageAlias) { - languageId = this._languageService.getModeIdForLanguageName(languageAlias); + languageId = this._languageService.getLanguageIdForLanguageName(languageAlias); } else if (this._options.editor) { languageId = this._options.editor.getModel()?.getLanguageId(); } diff --git a/src/vs/editor/common/services/languageService.ts b/src/vs/editor/common/services/languageService.ts index 96f64e7f74e14..47242b32d1756 100644 --- a/src/vs/editor/common/services/languageService.ts +++ b/src/vs/editor/common/services/languageService.ts @@ -37,13 +37,13 @@ export interface ILanguageService { // --- reading isRegisteredLanguageId(languageId: string): boolean; isRegisteredMimeType(mimeType: string): boolean; - getRegisteredModes(): string[]; + getRegisteredLanguageIds(): string[]; getRegisteredLanguageNames(): string[]; getExtensions(alias: string): string[]; getFilenames(alias: string): string[]; - getMimeForMode(languageId: string): string | null; + getMimeTypeForLanguageId(languageId: string): string | null; getLanguageName(languageId: string): string | null; - getModeIdForLanguageName(alias: string): string | null; + getLanguageIdForLanguageName(languageName: string): string | null; getModeIdByFilepathOrFirstLine(resource: URI, firstLine?: string): string | null; getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string): string | null; validateLanguageId(languageId: string): string | null; diff --git a/src/vs/editor/common/services/languageServiceImpl.ts b/src/vs/editor/common/services/languageServiceImpl.ts index 1ad8f8205a0ce..7ff72fb825bc2 100644 --- a/src/vs/editor/common/services/languageServiceImpl.ts +++ b/src/vs/editor/common/services/languageServiceImpl.ts @@ -84,8 +84,8 @@ export class LanguageService extends Disposable implements ILanguageService { return this._registry.isRegisteredMimeType(mimeType); } - public getRegisteredModes(): string[] { - return this._registry.getRegisteredModes(); + public getRegisteredLanguageIds(): string[] { + return this._registry.getRegisteredLanguageIds(); } public getRegisteredLanguageNames(): string[] { @@ -100,16 +100,16 @@ export class LanguageService extends Disposable implements ILanguageService { return this._registry.getFilenames(alias); } - public getMimeForMode(languageId: string): string | null { - return this._registry.getMimeForMode(languageId); + public getMimeTypeForLanguageId(languageId: string): string | null { + return this._registry.getMimeTypeForLanguageId(languageId); } public getLanguageName(languageId: string): string | null { return this._registry.getLanguageName(languageId); } - public getModeIdForLanguageName(alias: string): string | null { - return this._registry.getModeIdForLanguageNameLowercase(alias); + public getLanguageIdForLanguageName(alias: string): string | null { + return this._registry.getLanguageIdForLanguageName(alias); } public getModeIdByFilepathOrFirstLine(resource: URI | null, firstLine?: string): string | null { diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 51897867f2010..be0c753354893 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -265,7 +265,7 @@ export class LanguagesRegistry extends Disposable { return hasOwnProperty.call(this._mimeTypesMap, mimeType); } - public getRegisteredModes(): string[] { + public getRegisteredLanguageIds(): string[] { return Object.keys(this._languages); } @@ -280,7 +280,7 @@ export class LanguagesRegistry extends Disposable { return this._languages[languageId].name; } - public getModeIdForLanguageNameLowercase(languageNameLower: string): string | null { + public getLanguageIdForLanguageName(languageNameLower: string): string | null { if (!hasOwnProperty.call(this._lowercaseNameMap, languageNameLower)) { return null; } @@ -294,7 +294,7 @@ export class LanguagesRegistry extends Disposable { return this._languages[languageId].configurationFiles || []; } - public getMimeForMode(languageId: string): string | null { + public getMimeTypeForLanguageId(languageId: string): string | null { if (!hasOwnProperty.call(this._languages, languageId)) { return null; } diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index ed5c6ef154fa6..a56d548d17d6d 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -745,9 +745,9 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { const computeNewStateForEmbeddedMode = (enteringEmbeddedMode: string) => { // substitute language alias to known modes to support syntax highlighting - let enteringEmbeddedModeId = this._languageService.getModeIdForLanguageName(enteringEmbeddedMode); - if (enteringEmbeddedModeId) { - enteringEmbeddedMode = enteringEmbeddedModeId; + let enteringEmbeddedLanguageId = this._languageService.getLanguageIdForLanguageName(enteringEmbeddedMode); + if (enteringEmbeddedLanguageId) { + enteringEmbeddedMode = enteringEmbeddedLanguageId; } const embeddedModeData = this._getNestedEmbeddedModeData(enteringEmbeddedMode); diff --git a/src/vs/editor/test/common/services/languagesRegistry.test.ts b/src/vs/editor/test/common/services/languagesRegistry.test.ts index bccdf214fe96b..8fa5067fb3125 100644 --- a/src/vs/editor/test/common/services/languagesRegistry.test.ts +++ b/src/vs/editor/test/common/services/languagesRegistry.test.ts @@ -108,7 +108,7 @@ suite('LanguagesRegistry', () => { id: 'modeId' }]); - assert.deepStrictEqual(registry.getMimeForMode('modeId'), 'text/x-modeId'); + assert.deepStrictEqual(registry.getMimeTypeForLanguageId('modeId'), 'text/x-modeId'); registry.dispose(); }); @@ -121,7 +121,7 @@ suite('LanguagesRegistry', () => { mimetypes: ['text/modeId', 'text/modeId2'] }]); - assert.deepStrictEqual(registry.getMimeForMode('modeId'), 'text/modeId'); + assert.deepStrictEqual(registry.getMimeTypeForLanguageId('modeId'), 'text/modeId'); registry.dispose(); }); @@ -138,7 +138,7 @@ suite('LanguagesRegistry', () => { mimetypes: ['text/modeId'] }]); - assert.deepStrictEqual(registry.getMimeForMode('modeId'), 'text/x-modeId'); + assert.deepStrictEqual(registry.getMimeTypeForLanguageId('modeId'), 'text/x-modeId'); registry.dispose(); }); @@ -152,7 +152,7 @@ suite('LanguagesRegistry', () => { assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['a']); assert.deepStrictEqual(registry.getModeIdFromLanguageName('a'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a'), 'a'); assert.deepStrictEqual(registry.getLanguageName('a'), 'a'); registry._registerLanguages([{ @@ -164,9 +164,9 @@ suite('LanguagesRegistry', () => { assert.deepStrictEqual(registry.getModeIdFromLanguageName('a'), null); assert.deepStrictEqual(registry.getModeIdFromLanguageName('A1'), 'a'); assert.deepStrictEqual(registry.getModeIdFromLanguageName('A2'), null); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a1'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a2'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a1'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a2'), 'a'); assert.deepStrictEqual(registry.getLanguageName('a'), 'A1'); registry._registerLanguages([{ @@ -180,11 +180,11 @@ suite('LanguagesRegistry', () => { assert.deepStrictEqual(registry.getModeIdFromLanguageName('A2'), null); assert.deepStrictEqual(registry.getModeIdFromLanguageName('A3'), 'a'); assert.deepStrictEqual(registry.getModeIdFromLanguageName('A4'), null); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a1'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a2'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a3'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a4'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a1'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a2'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a3'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a4'), 'a'); assert.deepStrictEqual(registry.getLanguageName('a'), 'A3'); registry.dispose(); @@ -199,7 +199,7 @@ suite('LanguagesRegistry', () => { assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['a']); assert.deepStrictEqual(registry.getModeIdFromLanguageName('a'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a'), 'a'); assert.deepStrictEqual(registry.getLanguageName('a'), 'a'); registry._registerLanguages([{ @@ -210,8 +210,8 @@ suite('LanguagesRegistry', () => { assert.deepStrictEqual(registry.getRegisteredLanguageNames(), ['a']); assert.deepStrictEqual(registry.getModeIdFromLanguageName('a'), 'a'); assert.deepStrictEqual(registry.getModeIdFromLanguageName('b'), null); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('a'), 'a'); - assert.deepStrictEqual(registry.getModeIdForLanguageNameLowercase('b'), 'b'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('a'), 'a'); + assert.deepStrictEqual(registry.getLanguageIdForLanguageName('b'), 'b'); assert.deepStrictEqual(registry.getLanguageName('a'), 'a'); assert.deepStrictEqual(registry.getLanguageName('b'), null); diff --git a/src/vs/workbench/api/browser/mainThreadLanguages.ts b/src/vs/workbench/api/browser/mainThreadLanguages.ts index 2a8825b0d7cbc..05d2e7c52ca66 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguages.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguages.ts @@ -32,9 +32,9 @@ export class MainThreadLanguages implements MainThreadLanguagesShape { ) { this._proxy = _extHostContext.getProxy(ExtHostContext.ExtHostLanguages); - this._proxy.$acceptLanguageIds(_languageService.getRegisteredModes()); + this._proxy.$acceptLanguageIds(_languageService.getRegisteredLanguageIds()); this._disposables.add(_languageService.onLanguagesMaybeChanged(e => { - this._proxy.$acceptLanguageIds(_languageService.getRegisteredModes()); + this._proxy.$acceptLanguageIds(_languageService.getRegisteredLanguageIds()); })); } diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 973c95f155148..b79261dc74a10 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -56,7 +56,7 @@ abstract class MainThreadKernel implements INotebookKernel { this.description = data.description; this.detail = data.detail; this.kind = data.kind; - this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _languageService.getRegisteredModes(); + this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _languageService.getRegisteredLanguageIds(); this.implementsExecutionOrder = data.supportsExecutionOrder ?? false; this.localResourceRoot = URI.revive(data.extensionLocation); this.preloads = data.preloads?.map(u => ({ uri: URI.revive(u.uri), provides: u.provides })) ?? []; @@ -83,7 +83,7 @@ abstract class MainThreadKernel implements INotebookKernel { event.kind = true; } if (data.supportedLanguages !== undefined) { - this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : this._languageService.getRegisteredModes(); + this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : this._languageService.getRegisteredLanguageIds(); event.supportedLanguages = true; } if (data.supportsExecutionOrder !== undefined) { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index b7f6dc59e89fd..e731ae64b910c 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1120,7 +1120,7 @@ export class ChangeModeAction extends Action { const languages = this.languageService.getRegisteredLanguageNames(); const picks: QuickPickInput[] = languages.sort() .map(lang => { - const languageId = this.languageService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown'; + const languageId = this.languageService.getLanguageIdForLanguageName(lang.toLowerCase()) || 'unknown'; const extensions = this.languageService.getExtensions(lang).join(' '); let description: string; if (currentLanguageId === lang) { @@ -1217,8 +1217,8 @@ export class ChangeModeAction extends Action { if (resource) { // fire and forget to not slow things down this.languageDetectionService.detectLanguage(resource).then(detectedModeId => { - const chosenModeId = this.languageService.getModeIdForLanguageName(pick.label.toLowerCase()) || 'unknown'; - if (detectedModeId === currentModeId && currentModeId !== chosenModeId) { + const chosenLanguageId = this.languageService.getLanguageIdForLanguageName(pick.label.toLowerCase()) || 'unknown'; + if (detectedModeId === currentModeId && currentModeId !== chosenLanguageId) { // If they didn't choose the detected language (which should also be the active language if automatic detection is enabled) // then the automatic language detection was likely wrong and the user is correcting it. In this case, we want telemetry. this.telemetryService.publicLog2(AutomaticLanguageDetectionLikelyWrongId, { @@ -1251,7 +1251,7 @@ export class ChangeModeAction extends Action { const languages = this.languageService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { - const id = withNullAsUndefined(this.languageService.getModeIdForLanguageName(lang.toLowerCase())) || 'unknown'; + const id = withNullAsUndefined(this.languageService.getLanguageIdForLanguageName(lang.toLowerCase())) || 'unknown'; return { id, diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 00249a4652368..ecbb8d740ba33 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -210,7 +210,7 @@ export async function renderMarkdownDocument( } extensionService.whenInstalledExtensionsRegistered().then(async () => { let support: ITokenizationSupport | undefined; - const languageId = languageService.getModeIdForLanguageName(lang); + const languageId = languageService.getLanguageIdForLanguageName(lang); if (languageId) { languageService.triggerMode(languageId); support = await TokenizationRegistry.getPromise(languageId) ?? undefined; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts index 6b131a0da5c9f..a8ca648e42483 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts @@ -35,7 +35,7 @@ class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemP const languageId = cell.cellKind === CellKind.Markup ? 'markdown' : - (this._languageService.getModeIdForLanguageName(cell.language) || cell.language); + (this._languageService.getLanguageIdForLanguageName(cell.language) || cell.language); const text = this._languageService.getLanguageName(languageId) || languageId; const item = { text, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts b/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts index 0edd0d9c4e660..6ccc1520073ed 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts @@ -104,7 +104,7 @@ export class NotebookCodeRendererContribution extends Disposable { registeredMimeTypes.set(mimeType, true); }; - _languageService.getRegisteredModes().forEach(id => { + _languageService.getRegisteredLanguageIds().forEach(id => { registerCodeRendererContrib(`text/x-${id}`, id); }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts index 6545eeb70ce07..c6165cac38f75 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts @@ -592,7 +592,7 @@ export function insertCell( const nextIndex = ui ? viewModel.getNextVisibleCellIndex(index) : index + 1; let language; if (type === CellKind.Code) { - const supportedLanguages = activeKernel?.supportedLanguages ?? languageService.getRegisteredModes(); + const supportedLanguages = activeKernel?.supportedLanguages ?? languageService.getRegisteredLanguageIds(); const defaultLanguage = supportedLanguages[0] || 'plaintext'; if (cell?.cellKind === CellKind.Code) { language = cell.language; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 2bc48672377a6..e3a1509c64fb7 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -377,7 +377,7 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction { const languages = this.languageService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { - const description: string = nls.localize('languageDescriptionConfigured', "({0})", this.languageService.getModeIdForLanguageName(lang.toLowerCase())); + const description: string = nls.localize('languageDescriptionConfigured', "({0})", this.languageService.getLanguageIdForLanguageName(lang.toLowerCase())); // construct a fake resource to be able to show nice icons if any let fakeResource: URI | undefined; const extensions = this.languageService.getExtensions(lang); @@ -53,7 +53,7 @@ export class ConfigureLanguageBasedSettingsAction extends Action { await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language") }) .then(pick => { if (pick) { - const languageId = this.languageService.getModeIdForLanguageName(pick.label.toLowerCase()); + const languageId = this.languageService.getLanguageIdForLanguageName(pick.label.toLowerCase()); if (typeof languageId === 'string') { return this.preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: `[${languageId}]`, edit: true } }); } diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index e352adb6bbf75..0fbb7c2abe83c 100644 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -88,13 +88,13 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir } const dir = envService.snippetsHome; - for (const mode of languageService.getRegisteredModes()) { - const label = languageService.getLanguageName(mode); - if (label && !seen.has(mode)) { + for (const languageId of languageService.getRegisteredLanguageIds()) { + const label = languageService.getLanguageName(languageId); + if (label && !seen.has(languageId)) { future.push({ - label: mode, + label: languageId, description: `(${label})`, - filepath: joinPath(dir, `${mode}.json`), + filepath: joinPath(dir, `${languageId}.json`), hint: true }); } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts index 751aa6d35c460..bd781f2fea36d 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts @@ -71,7 +71,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi const renderer = new marked.Renderer(); renderer.code = (code, lang) => { i++; - const languageId = this.languageService.getModeIdForLanguageName(lang) || ''; + const languageId = this.languageService.getLanguageIdForLanguageName(lang) || ''; const languageSelection = this.languageService.create(languageId); // Create all models for this resource in one go... we'll need them all and we don't want to re-parse markdown each time const model = this.modelService.createModel(code, languageSelection, resource.with({ fragment: `${i}.${lang}` })); diff --git a/src/vs/workbench/services/mode/common/workbenchLanguageService.ts b/src/vs/workbench/services/mode/common/workbenchLanguageService.ts index d647fad25cc5b..aa7f5701baeca 100644 --- a/src/vs/workbench/services/mode/common/workbenchLanguageService.ts +++ b/src/vs/workbench/services/mode/common/workbenchLanguageService.ts @@ -165,9 +165,9 @@ export class WorkbenchLanguageService extends LanguageService { if (configuration.files?.associations) { Object.keys(configuration.files.associations).forEach(pattern => { const langId = configuration.files.associations[pattern]; - const mimetype = this.getMimeForMode(langId) || `text/x-${langId}`; + const mimeType = this.getMimeTypeForLanguageId(langId) || `text/x-${langId}`; - mime.registerTextMime({ id: langId, mime: mimetype, filepattern: pattern, userConfigured: true }); + mime.registerTextMime({ id: langId, mime: mimeType, filepattern: pattern, userConfigured: true }); }); } From d4ff3b001d57032b43ff942f1e761fb5c7cf4e6d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Dec 2021 06:09:28 -0800 Subject: [PATCH 0430/2210] Skip failing tests --- .../contrib/terminal/test/browser/terminalService.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts index ab9dbe73b5d2a..d6e562276eee7 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts @@ -55,7 +55,7 @@ suite('Workbench - TerminalService', () => { instantiationService.stub(ITerminalService, terminalService); }); - suite('safeDisposeTerminal', () => { + suite.skip('safeDisposeTerminal', () => { test('should not show prompt when confirmOnKill is never', async () => { setConfirmOnKill(configurationService, 'never'); await new Promise(r => { From d24eb562d9d8d5e1e003a9c252107756ef448822 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 8 Dec 2021 15:05:48 +0100 Subject: [PATCH 0431/2210] fixes #138605 --- .../contrib/scm/common/scmService.ts | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 29799b5371f80..42a1b1d3a6b09 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -65,7 +65,6 @@ class SCMInput implements ISCMInput { private readonly _onDidChangeValidationMessage = new Emitter(); readonly onDidChangeValidationMessage: Event = this._onDidChangeValidationMessage.event; - private _validateInput: IInputValidator = () => Promise.resolve(undefined); get validateInput(): IInputValidator { @@ -82,23 +81,58 @@ class SCMInput implements ISCMInput { private historyNavigator: HistoryNavigator2; + private static didCleanup = false; + private static migrateAndGarbageCollectStorage(storageService: IStorageService): void { + if (SCMInput.didCleanup) { + return; + } + + const keys = storageService.keys(StorageScope.GLOBAL, StorageTarget.USER) + .filter(key => key.startsWith('scm/input:')); + + for (const key of keys) { + try { + const history = JSON.parse(storageService.get(key, StorageScope.GLOBAL, '[]')); + + if (Array.isArray(history)) { + if (history.length === 0 || history.length === 1 && history[0] === '') { + // remove empty histories + storageService.remove(key, StorageScope.GLOBAL); + } else { + // migrate existing histories to have a timestamp + storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.GLOBAL, StorageTarget.USER); + } + } else if (Array.isArray(history?.history) && Number.isInteger(history?.timestamp) && new Date().getTime() - history?.timestamp > 2592000000) { + // garbage collect after 30 days + storageService.remove(key, StorageScope.GLOBAL); + } + } catch { + // remove unparseable entries + storageService.remove(key, StorageScope.GLOBAL); + } + } + + SCMInput.didCleanup = true; + } + constructor( readonly repository: ISCMRepository, @IStorageService private storageService: IStorageService ) { - const historyKey = `scm/input:${this.repository.provider.label}:${this.repository.provider.rootUri?.path}`; + SCMInput.migrateAndGarbageCollectStorage(storageService); + + const key = this.repository.provider.rootUri ? `scm/input:${this.repository.provider.label}:${this.repository.provider.rootUri?.path}` : undefined; let history: string[] | undefined; - let rawHistory = this.storageService.get(historyKey, StorageScope.GLOBAL, ''); - if (rawHistory) { + if (key) { try { - history = JSON.parse(rawHistory); + history = JSON.parse(this.storageService.get(key, StorageScope.GLOBAL, '')).history; } catch { // noop } } - if (!history || history.length === 0) { + if (!Array.isArray(history)) { history = [this._value]; } else { this._value = history[history.length - 1]; @@ -106,15 +140,19 @@ class SCMInput implements ISCMInput { this.historyNavigator = new HistoryNavigator2(history, 50); - this.storageService.onWillSaveState(e => { - if (this.historyNavigator.isAtEnd()) { - this.historyNavigator.replaceLast(this._value); - } + if (key) { + this.storageService.onWillSaveState(_ => { + if (this.historyNavigator.isAtEnd()) { + this.historyNavigator.replaceLast(this._value); + } - if (this.repository.provider.rootUri) { - this.storageService.store(historyKey, JSON.stringify([...this.historyNavigator]), StorageScope.GLOBAL, StorageTarget.USER); - } - }); + const history = [...this.historyNavigator]; + + if (history.length > 1 || (history.length === 1 && history[0] !== '')) { + storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.GLOBAL, StorageTarget.USER); + } + }); + } } setValue(value: string, transient: boolean, reason?: SCMInputChangeReason) { From f0e7d8545aa99e5a796028cf8466b0f6a2ae2940 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 8 Dec 2021 15:18:09 +0100 Subject: [PATCH 0432/2210] we check in /build ts output to the repo cc @lramos15 --- build/lib/util.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/lib/util.js b/build/lib/util.js index c4abffffcf8fc..3e15bd5e42261 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -286,7 +286,10 @@ function acquireWebNodePaths() { let entryPoint = (_a = packageData.browser) !== null && _a !== void 0 ? _a : packageData.main; // On rare cases a package doesn't have an entrypoint so we assume it has a dist folder with a min.js if (!entryPoint) { - console.warn(`No entry point for ${key} assuming dist/${key}.min.js`); + // TODO @lramos15 remove this when jschardet adds an entrypoint so we can warn on all packages w/out entrypoint + if (key !== 'jschardet') { + console.warn(`No entry point for ${key} assuming dist/${key}.min.js`); + } entryPoint = `dist/${key}.min.js`; } // Remove any starting path information so it's all relative info From 18214d3dbd4582423812aee921b15c8f6e4fbd33 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 8 Dec 2021 15:19:42 +0100 Subject: [PATCH 0433/2210] fix EOL for generated extensionsApiProposals.ts --- build/lib/compilation.js | 4 ++-- build/lib/compilation.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/lib/compilation.js b/build/lib/compilation.js index b81e714fa41de..beb69369e15eb 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -198,11 +198,11 @@ function apiProposalNamesGenerator() { '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', '', 'export const allApiProposals = Object.freeze({', - `${proposalNames.map(t => `\t${t[0]}: '${t[1]}'`).join(',\n')}`, + `${proposalNames.map(t => `\t${t[0]}: '${t[1]}'`).join(`,${os.EOL}`)}`, '});', 'export type ApiProposalName = keyof typeof allApiProposals;', '', - ].join('\n'); + ].join(os.EOL); const outFile = path.join(dtsFolder, '../vs/workbench/services/extensions/common/extensionsApiProposals.ts'); if (fs.readFileSync(outFile).toString() !== source) { fs.writeFileSync(outFile, source); diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 892acaa421687..73e54fc982669 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -240,11 +240,11 @@ function apiProposalNamesGenerator() { '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', '', 'export const allApiProposals = Object.freeze({', - `${proposalNames.map(t => `\t${t[0]}: '${t[1]}'`).join(',\n')}`, + `${proposalNames.map(t => `\t${t[0]}: '${t[1]}'`).join(`,${os.EOL}`)}`, '});', 'export type ApiProposalName = keyof typeof allApiProposals;', '', - ].join('\n'); + ].join(os.EOL); const outFile = path.join(dtsFolder, '../vs/workbench/services/extensions/common/extensionsApiProposals.ts'); From 5e54e757630d09544e47ce137caed99e47fb55d6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 15:23:05 +0100 Subject: [PATCH 0434/2210] Improves docs in cursorCollection --- src/vs/editor/common/controller/cursorCollection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/controller/cursorCollection.ts b/src/vs/editor/common/controller/cursorCollection.ts index 26b358088fd8a..3a98632d9e19d 100644 --- a/src/vs/editor/common/controller/cursorCollection.ts +++ b/src/vs/editor/common/controller/cursorCollection.ts @@ -15,7 +15,7 @@ export class CursorCollection { private context: CursorContext; /** - * `cursors[0]` is the primary cursor. + * `cursors[0]` is the primary cursor, thus `cursors.length >= 1` is always true. * `cursors.slice(1)` are secondary cursors. */ private cursors: Cursor[]; From 18cba4882d5b6eb8844e848a73dbd6ca282eca47 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Dec 2021 15:23:22 +0100 Subject: [PATCH 0435/2210] smoke - more logging --- test/automation/src/application.ts | 4 +-- test/automation/src/code.ts | 16 +++++---- test/automation/src/electronDriver.ts | 12 +++---- test/automation/src/playwrightDriver.ts | 19 +++++------ test/automation/src/terminal.ts | 2 +- .../src/areas/workbench/data-loss.test.ts | 4 ++- test/smoke/src/areas/workbench/launch.test.ts | 3 +- .../src/areas/workbench/localization.test.ts | 3 +- test/smoke/src/main.ts | 21 ++++++------ test/smoke/src/utils.ts | 33 +++++++------------ 10 files changed, 57 insertions(+), 60 deletions(-) diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 4a58db4aa80b9..0b2bbedee051d 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { Workbench } from './workbench'; import { Code, spawn, SpawnOptions } from './code'; -import { Logger } from './logger'; +import { Logger, measureAndLog } from './logger'; export const enum Quality { Dev, @@ -94,7 +94,7 @@ export class Application { async captureScreenshot(name: string): Promise { if (this.options.screenshotsPath) { - const raw = await this.code.capturePage(); + const raw = await measureAndLog(this.code.capturePage(), 'capturePage', this.options.logger); const buffer = Buffer.from(raw, 'base64'); const screenshotPath = path.join(this.options.screenshotsPath, `${name}.png`); this.logger.log('Screenshot recorded:', screenshotPath); diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 0e30e8a1690e9..ac138b770dedd 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -9,7 +9,7 @@ import * as cp from 'child_process'; import { IDriver, IDisposable, IElement, Thenable, ILocalizedStrings, ILocaleInfo } from './driver'; import { launch as launchElectron } from './electronDriver'; import { launch as launchPlaywright } from './playwrightDriver'; -import { Logger } from './logger'; +import { Logger, measureAndLog } from './logger'; import { copyExtension } from './extensions'; const repoPath = path.join(__dirname, '../../..'); @@ -39,7 +39,7 @@ export async function spawn(options: SpawnOptions): Promise { throw new Error('Smoke test process has terminated, refusing to spawn Code'); } - await copyExtension(repoPath, options.extensionsPath, 'vscode-notebook-tests'); + await measureAndLog(copyExtension(repoPath, options.extensionsPath, 'vscode-notebook-tests'), 'copyExtension(vscode-notebook-tests)', options.logger); // Browser smoke tests if (options.web) { @@ -135,12 +135,16 @@ export class Code { async startTracing(name: string): Promise { const windowId = await this.getActiveWindowId(); - return await this.driver.startTracing(windowId, name); + if (typeof this.driver.startTracing === 'function') { // added only in 1.64 + return await this.driver.startTracing(windowId, name); + } } async stopTracing(name: string, persist: boolean): Promise { const windowId = await this.getActiveWindowId(); - return await this.driver.stopTracing(windowId, name, persist); + if (typeof this.driver.stopTracing === 'function') { // added only in 1.64 + return await this.driver.stopTracing(windowId, name, persist); + } } async waitForWindowIds(fn: (windowIds: number[]) => boolean): Promise { @@ -153,7 +157,7 @@ export class Code { } async exit(): Promise { - return new Promise((resolve, reject) => { + return measureAndLog(new Promise((resolve, reject) => { let done = false; // Start the exit flow via driver @@ -190,7 +194,7 @@ export class Code { })(); }).finally(() => { this.dispose(); - }); + }), 'Code#exit()', this.logger); } async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean, retryCount?: number): Promise { diff --git a/test/automation/src/electronDriver.ts b/test/automation/src/electronDriver.ts index 51f59af2b8dfc..823b4f0fb42c7 100644 --- a/test/automation/src/electronDriver.ts +++ b/test/automation/src/electronDriver.ts @@ -13,7 +13,7 @@ import { promisify } from 'util'; import * as kill from 'tree-kill'; import { copyExtension } from './extensions'; import { URI } from 'vscode-uri'; -import { Logger } from './logger'; +import { Logger, measureAndLog } from './logger'; const repoPath = path.join(__dirname, '../../..'); @@ -22,7 +22,7 @@ export async function launch(codePath: string | undefined, userDataDir: string, const logsPath = path.join(repoPath, '.build', 'logs', remote ? 'smoke-tests-remote' : 'smoke-tests'); const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); - const driverIPCHandle = await createDriverHandle(); + const driverIPCHandle = await measureAndLog(createDriverHandle(), 'createDriverHandle', logger); const args = [ workspacePath, @@ -50,7 +50,7 @@ export async function launch(codePath: string | undefined, userDataDir: string, if (codePath) { // running against a build: copy the test resolver extension - await copyExtension(repoPath, extensionsPath, 'vscode-test-resolver'); + await measureAndLog(copyExtension(repoPath, extensionsPath, 'vscode-test-resolver'), 'copyExtension(vscode-test-resolver)', logger); } args.push('--enable-proposed-api=vscode.vscode-test-resolver'); const remoteDataDir = `${userDataDir}-server`; @@ -60,7 +60,7 @@ export async function launch(codePath: string | undefined, userDataDir: string, // running against a build: copy the test resolver extension into remote extensions dir const remoteExtensionsDir = path.join(remoteDataDir, 'extensions'); mkdirp.sync(remoteExtensionsDir); - await copyExtension(repoPath, remoteExtensionsDir, 'vscode-notebook-tests'); + await measureAndLog(copyExtension(repoPath, remoteExtensionsDir, 'vscode-notebook-tests'), 'copyExtension(vscode-notebook-tests)', logger); } env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; @@ -109,7 +109,7 @@ export async function launch(codePath: string | undefined, userDataDir: string, while (true) { try { - const { client, driver } = await connectElectronDriver(outPath, driverIPCHandle); + const { client, driver } = await measureAndLog(connectElectronDriver(outPath, driverIPCHandle), 'connectElectronDriver()', logger); return { electronProcess, client, driver }; } catch (err) { @@ -118,7 +118,7 @@ export async function launch(codePath: string | undefined, userDataDir: string, logger.log(`Error connecting driver: ${err}. Giving up...`); try { - await promisify(kill)(electronProcess.pid!); + await measureAndLog(promisify(kill)(electronProcess.pid!), 'Kill Electron after failing to connect', logger); } catch (error) { logger.log(`Error tearing down electron client (pid: ${electronProcess.pid}): ${error}`); } diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index 73f7543260ef7..d85f2b9cfe194 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -12,8 +12,7 @@ import { IDriver, IDisposable, IWindowDriver } from './driver'; import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; import { PageFunction } from 'playwright-core/types/structs'; -import { Logger } from './logger'; -import { measureAndLog } from '.'; +import { Logger, measureAndLog } from './logger'; const width = 1200; const height = 800; @@ -223,7 +222,7 @@ export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_P async function launchServer(userDataDir: string, codeServerPath: string | undefined, extensionsPath: string, verbose: boolean, logger: Logger) { const agentFolder = userDataDir; - await promisify(mkdir)(agentFolder); + await measureAndLog(promisify(mkdir)(agentFolder), `mkdir(${agentFolder})`, logger); const env = { VSCODE_AGENT_FOLDER: agentFolder, VSCODE_REMOTE_SERVER_PATH: codeServerPath, @@ -271,22 +270,22 @@ async function launchServer(userDataDir: string, codeServerPath: string | undefi return { serverProcess, - endpoint: await waitForEndpoint(serverProcess) + endpoint: await measureAndLog(waitForEndpoint(serverProcess), 'waitForEndpoint(serverProcess)', logger) }; } async function launchBrowser(options: PlaywrightOptions, endpoint: string, workspacePath: string, logger: Logger) { - const browser = await playwright[options.browser ?? 'chromium'].launch({ headless: options.headless ?? false }); - const context = await browser.newContext(); + const browser = await measureAndLog(playwright[options.browser ?? 'chromium'].launch({ headless: options.headless ?? false }), 'playwright#launch', logger); + const context = await measureAndLog(browser.newContext(), 'browser.newContext', logger); try { - await context.tracing.start({ screenshots: true, snapshots: true, sources: true }); + await measureAndLog(context.tracing.start({ screenshots: true, snapshots: true, sources: true }), 'context.tracing.start()', logger); } catch (error) { logger.log(`Failed to start playwright tracing: ${error}`); // do not fail the build when this fails } - const page = await context.newPage(); - await page.setViewportSize({ width, height }); + const page = await measureAndLog(context.newPage(), 'context.newPage()', logger); + await measureAndLog(page.setViewportSize({ width, height }), 'page.setViewportSize', logger); page.on('pageerror', async (error) => logger.log(`Playwright ERROR: page error: ${error}`)); page.on('crash', page => logger.log('Playwright ERROR: page crash')); @@ -297,7 +296,7 @@ async function launchBrowser(options: PlaywrightOptions, endpoint: string, works }); const payloadParam = `[["enableProposedApi",""],["skipWelcome","true"]]`; - await page.goto(`${endpoint}&folder=vscode-remote://localhost:9888${URI.file(workspacePath!).path}&payload=${payloadParam}`); + await measureAndLog(page.goto(`${endpoint}&folder=vscode-remote://localhost:9888${URI.file(workspacePath!).path}&payload=${payloadParam}`), 'page.goto()', logger); return { browser, context, page }; } diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 5813f0215edb7..fe2589f726a04 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { QuickInput } from '.'; +import { QuickInput } from './quickinput'; import { Code } from './code'; import { QuickAccess } from './quickaccess'; diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 24be2f5743ef8..0521bcb4859aa 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -5,13 +5,14 @@ import { Application, ApplicationOptions, Quality } from '../../../../automation/out'; import { ParsedArgs } from 'minimist'; -import { installCommonAfterHandlers, getRandomUserDataDir, startApp, timeout } from '../../utils'; +import { installCommonAfterHandlers, getRandomUserDataDir, startApp, timeout, installCommonBeforeEachHandler } from '../../utils'; export function setup(opts: ParsedArgs) { describe('Data Loss (insiders -> insiders)', () => { let app: Application | undefined = undefined; + installCommonBeforeEachHandler(); installCommonAfterHandlers(opts, () => app); it('verifies opened editors are restored', async function () { @@ -97,6 +98,7 @@ export function setup(opts: ParsedArgs) { let insidersApp: Application | undefined = undefined; let stableApp: Application | undefined = undefined; + installCommonBeforeEachHandler(); installCommonAfterHandlers(opts, () => insidersApp ?? stableApp, async () => stableApp?.stop()); it('verifies opened editors are restored', async function () { diff --git a/test/smoke/src/areas/workbench/launch.test.ts b/test/smoke/src/areas/workbench/launch.test.ts index dc17c9dc14c0a..d9e54876fe9e2 100644 --- a/test/smoke/src/areas/workbench/launch.test.ts +++ b/test/smoke/src/areas/workbench/launch.test.ts @@ -6,13 +6,14 @@ import minimist = require('minimist'); import { join } from 'path'; import { Application } from '../../../../automation'; -import { installCommonAfterHandlers, startApp } from '../../utils'; +import { installCommonAfterHandlers, installCommonBeforeEachHandler, startApp } from '../../utils'; export function setup(args: minimist.ParsedArgs) { describe('Launch', () => { let app: Application | undefined; + installCommonBeforeEachHandler(); installCommonAfterHandlers(args, () => app); it(`verifies that application launches when user data directory has non-ascii characters`, async function () { diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index 27ce308a90230..ebc6841f473aa 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -5,7 +5,7 @@ import minimist = require('minimist'); import { Application, Quality } from '../../../../automation'; -import { installCommonAfterHandlers, startApp } from '../../utils'; +import { installCommonAfterHandlers, installCommonBeforeEachHandler, startApp } from '../../utils'; export function setup(args: minimist.ParsedArgs) { @@ -13,6 +13,7 @@ export function setup(args: minimist.ParsedArgs) { let app: Application | undefined = undefined; + installCommonBeforeEachHandler(); installCommonAfterHandlers(args, () => app); it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () { diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index ce06c48fff80d..20aec66e4d076 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -13,7 +13,7 @@ import * as rimraf from 'rimraf'; import * as mkdirp from 'mkdirp'; import * as vscodetest from 'vscode-test'; import fetch from 'node-fetch'; -import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger } from '../../automation'; +import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger, measureAndLog } from '../../automation'; import { timeout } from './utils'; import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; @@ -216,7 +216,6 @@ async function setupRepository(logger: Logger): Promise { } else { cp.execSync(`cp -R "${opts['test-repo']}" "${workspacePath}"`); } - } else { if (!fs.existsSync(workspacePath)) { logger.log('Cloning test project repository...'); @@ -244,13 +243,13 @@ async function ensureStableCode(logger: Logger): Promise { if (!stableCodePath) { const { major, minor } = parseVersion(version!); const majorMinorVersion = `${major}.${minor - 1}`; - const versionsReq = await fetch('https://update.code.visualstudio.com/api/releases/stable', { headers: { 'x-api-version': '2' } }); + const versionsReq = await measureAndLog(fetch('https://update.code.visualstudio.com/api/releases/stable', { headers: { 'x-api-version': '2' } }), 'versionReq', logger); if (!versionsReq.ok) { throw new Error('Could not fetch releases from update server'); } - const versions: { version: string }[] = await versionsReq.json(); + const versions: { version: string }[] = await measureAndLog(versionsReq.json(), 'versionReq.json()', logger); const prefix = `${majorMinorVersion}.`; const previousVersion = versions.find(v => v.version.startsWith(prefix)); @@ -260,10 +259,10 @@ async function ensureStableCode(logger: Logger): Promise { logger.log(`Found VS Code v${version}, downloading previous VS Code version ${previousVersion.version}...`); - const stableCodeExecutable = await vscodetest.download({ + const stableCodeExecutable = await measureAndLog(vscodetest.download({ cachePath: path.join(os.tmpdir(), 'vscode-test'), version: previousVersion.version - }); + }), 'download stable code', logger); if (process.platform === 'darwin') { // Visual Studio Code.app/Contents/MacOS/Electron @@ -287,8 +286,8 @@ async function setup(logger: Logger): Promise { logger.log('Test data:', testDataPath); logger.log('Preparing smoketest setup...'); - await ensureStableCode(logger); - await setupRepository(logger); + await measureAndLog(ensureStableCode(logger), 'ensureStableCode', logger); + await measureAndLog(setupRepository(logger), 'setupRepository', logger); logger.log('Smoketest setup done!\n'); } @@ -350,7 +349,7 @@ after(async function () { // // Refs: https://github.com/microsoft/vscode/issues/137725 let deleted = false; - await Promise.race([ + await measureAndLog(Promise.race([ new Promise((resolve, reject) => rimraf(testDataPath, { maxBusyTries: 10 }, error => { if (error) { reject(error); @@ -364,9 +363,9 @@ after(async function () { throw new Error('giving up after 30s'); } }) - ]); + ]), 'rimraf(testDataPath)', this.defaultOptions.logger); } catch (error) { - this.options.logger(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`); + this.defaultOptions.logger(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`); } }); diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index 76f465d50814b..d4d8eabe63466 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -26,23 +26,20 @@ export function installCommonTestHandlers(args: minimist.ParsedArgs, optionsTran export function installCommonBeforeHandlers(args: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { before(async function () { - const testTitle = this.currentTest?.title; - - this.app = await startApp(args, this.defaultOptions, async opts => { - opts.testTitle = testTitle; - - if (optionsTransform) { - opts = await optionsTransform(opts); - } - - return opts; - }); + this.app = await startApp(args, this.defaultOptions, optionsTransform); }); + installCommonBeforeEachHandler(); +} + +export function installCommonBeforeEachHandler() { beforeEach(async function () { - if (this.app) { - await this.app.startTracing(this.currentTest?.title); - } + const testTitle = this.currentTest?.title; + this.defaultOptions.logger.log(''); + this.defaultOptions.logger.log(`>>> Test start: ${testTitle} <<<`); + this.defaultOptions.logger.log(''); + + await this.app?.startTracing(testTitle); }); } @@ -58,10 +55,6 @@ export async function startApp(args: minimist.ParsedArgs, options: ApplicationOp await app.start(); - if (options.testTitle) { - app.logger.log('Test start:', options.testTitle); - } - return app; } @@ -98,9 +91,7 @@ export function installCommonAfterHandlers(opts: minimist.ParsedArgs, appFn?: () }); afterEach(async function () { - if (this.app) { - await this.app.stopTracing(this.currentTest?.title, this.currentTest?.state === 'failed'); - } + await this.app?.stopTracing(this.currentTest?.title, this.currentTest?.state === 'failed'); }); } From 36687f38cb5b19f68dbe5c8e8269b28c46131242 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Dec 2021 06:26:08 -0800 Subject: [PATCH 0436/2210] Fix safeDisposeTerminal tests --- .../test/browser/terminalService.test.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts index d6e562276eee7..c26cd02627e6b 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { fail } from 'assert'; +import { Emitter } from 'vs/base/common/event'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { TerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -55,13 +56,20 @@ suite('Workbench - TerminalService', () => { instantiationService.stub(ITerminalService, terminalService); }); - suite.skip('safeDisposeTerminal', () => { + suite('safeDisposeTerminal', () => { + let onExitEmitter: Emitter; + + setup(() => { + onExitEmitter = new Emitter(); + }); + test('should not show prompt when confirmOnKill is never', async () => { setConfirmOnKill(configurationService, 'never'); await new Promise(r => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Editor, hasChildProcesses: true, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -69,6 +77,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -79,6 +88,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Editor, hasChildProcesses: true, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -87,6 +97,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Editor, hasChildProcesses: true, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -97,6 +108,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -109,6 +121,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: false, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -117,6 +130,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: false, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -132,6 +146,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -144,6 +159,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: false, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -152,6 +168,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: false, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); @@ -167,6 +184,7 @@ suite('Workbench - TerminalService', () => { terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, + onExit: onExitEmitter.event, dispose: () => r() } as Partial as any); }); From 920fc15008b31907de3b6885a092253edab5b71e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 15:34:29 +0100 Subject: [PATCH 0437/2210] Remove `ILanguageService.getModeId` --- .../editor/common/services/getIconClasses.ts | 10 ++-- .../editor/common/services/languageService.ts | 3 +- .../common/services/languageServiceImpl.ts | 10 ++-- .../common/services/languagesRegistry.ts | 11 ++-- src/vs/editor/standalone/browser/colorizer.ts | 20 +++---- .../standalone/common/monarch/monarchLexer.ts | 53 ++++++------------- .../common/model/notebookCellTextModel.ts | 2 +- 7 files changed, 46 insertions(+), 63 deletions(-) diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index d842a2c6b4231..0d570c27b9f61 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -69,7 +69,7 @@ function detectModeId(modelService: IModelService, languageService: ILanguageSer return null; // we need a resource at least } - let modeId: string | null = null; + let languageId: string | null = null; // Data URI: check for encoded metadata if (resource.scheme === Schemas.data) { @@ -77,7 +77,7 @@ function detectModeId(modelService: IModelService, languageService: ILanguageSer const mime = metadata.get(DataUri.META_DATA_MIME); if (mime) { - modeId = languageService.getModeId(mime); + languageId = languageService.getLanguageIdForMimeType(mime); } } @@ -85,13 +85,13 @@ function detectModeId(modelService: IModelService, languageService: ILanguageSer else { const model = modelService.getModel(resource); if (model) { - modeId = model.getLanguageId(); + languageId = model.getLanguageId(); } } // only take if the mode is specific (aka no just plain text) - if (modeId && modeId !== PLAINTEXT_MODE_ID) { - return modeId; + if (languageId && languageId !== PLAINTEXT_MODE_ID) { + return languageId; } // otherwise fallback to path based detection diff --git a/src/vs/editor/common/services/languageService.ts b/src/vs/editor/common/services/languageService.ts index 47242b32d1756..f800fb8fdfd88 100644 --- a/src/vs/editor/common/services/languageService.ts +++ b/src/vs/editor/common/services/languageService.ts @@ -36,7 +36,6 @@ export interface ILanguageService { // --- reading isRegisteredLanguageId(languageId: string): boolean; - isRegisteredMimeType(mimeType: string): boolean; getRegisteredLanguageIds(): string[]; getRegisteredLanguageNames(): string[]; getExtensions(alias: string): string[]; @@ -44,8 +43,8 @@ export interface ILanguageService { getMimeTypeForLanguageId(languageId: string): string | null; getLanguageName(languageId: string): string | null; getLanguageIdForLanguageName(languageName: string): string | null; + getLanguageIdForMimeType(mimeType: string): string | null; getModeIdByFilepathOrFirstLine(resource: URI, firstLine?: string): string | null; - getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string): string | null; validateLanguageId(languageId: string): string | null; getConfigurationFiles(languageId: string): URI[]; diff --git a/src/vs/editor/common/services/languageServiceImpl.ts b/src/vs/editor/common/services/languageServiceImpl.ts index 7ff72fb825bc2..60f3c706d071a 100644 --- a/src/vs/editor/common/services/languageServiceImpl.ts +++ b/src/vs/editor/common/services/languageServiceImpl.ts @@ -80,10 +80,6 @@ export class LanguageService extends Disposable implements ILanguageService { return this._registry.isRegisteredLanguageId(languageId); } - public isRegisteredMimeType(mimeType: string): boolean { - return this._registry.isRegisteredMimeType(mimeType); - } - public getRegisteredLanguageIds(): string[] { return this._registry.getRegisteredLanguageIds(); } @@ -112,12 +108,16 @@ export class LanguageService extends Disposable implements ILanguageService { return this._registry.getLanguageIdForLanguageName(alias); } + public getLanguageIdForMimeType(mimeType: string): string | null { + return this._registry.getLanguageIdForMimeType(mimeType); + } + public getModeIdByFilepathOrFirstLine(resource: URI | null, firstLine?: string): string | null { const modeIds = this._registry.getModeIdsFromFilepathOrFirstLine(resource, firstLine); return firstOrDefault(modeIds, null); } - public getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): string | null { + private getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): string | null { const modeIds = this._registry.extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds); return firstOrDefault(modeIds, null); } diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index be0c753354893..668f0c7e0993c 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -261,10 +261,6 @@ export class LanguagesRegistry extends Disposable { return hasOwnProperty.call(this._languages, languageId); } - public isRegisteredMimeType(mimeType: string): boolean { - return hasOwnProperty.call(this._mimeTypesMap, mimeType); - } - public getRegisteredLanguageIds(): string[] { return Object.keys(this._languages); } @@ -287,6 +283,13 @@ export class LanguagesRegistry extends Disposable { return this._lowercaseNameMap[languageNameLower]; } + public getLanguageIdForMimeType(mimeType: string): string | null { + if (hasOwnProperty.call(this._mimeTypesMap, mimeType)) { + return this._mimeTypesMap[mimeType]; + } + return null; + } + public getConfigurationFiles(languageId: string): URI[] { if (!hasOwnProperty.call(this._languages, languageId)) { return []; diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index 66fd58bbcecd8..57dbb5dfb5e68 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -31,11 +31,12 @@ export class Colorizer { public static colorizeElement(themeService: IStandaloneThemeService, languageService: ILanguageService, domNode: HTMLElement, options: IColorizerElementOptions): Promise { options = options || {}; let theme = options.theme || 'vs'; - let mimeType = options.mimeType || domNode.getAttribute('lang') || domNode.getAttribute('data-lang'); + const mimeType = options.mimeType || domNode.getAttribute('lang') || domNode.getAttribute('data-lang'); if (!mimeType) { console.error('Mode not detected'); return Promise.resolve(); } + const languageId = languageService.getLanguageIdForMimeType(mimeType) || mimeType; themeService.setTheme(theme); @@ -45,10 +46,10 @@ export class Colorizer { const trustedhtml = ttPolicy?.createHTML(str) ?? str; domNode.innerHTML = trustedhtml as string; }; - return this.colorize(languageService, text || '', mimeType, options).then(render, (err) => console.error(err)); + return this.colorize(languageService, text || '', languageId, options).then(render, (err) => console.error(err)); } - public static colorize(languageService: ILanguageService, text: string, mimeType: string, options: IColorizerOptions | null | undefined): Promise { + public static colorize(languageService: ILanguageService, text: string, languageId: string, options: IColorizerOptions | null | undefined): Promise { const languageIdCodec = languageService.languageIdCodec; let tabSize = 4; if (options && typeof options.tabSize === 'number') { @@ -59,20 +60,19 @@ export class Colorizer { text = text.substr(1); } let lines = strings.splitLines(text); - let language = languageService.getModeId(mimeType); - if (!language) { + if (!languageService.isRegisteredLanguageId(languageId)) { return Promise.resolve(_fakeColorize(lines, tabSize, languageIdCodec)); } // Send out the event to create the mode - languageService.triggerMode(language); + languageService.triggerMode(languageId); - const tokenizationSupport = TokenizationRegistry.get(language); + const tokenizationSupport = TokenizationRegistry.get(languageId); if (tokenizationSupport) { return _colorize(lines, tabSize, tokenizationSupport, languageIdCodec); } - const tokenizationSupportPromise = TokenizationRegistry.getPromise(language); + const tokenizationSupportPromise = TokenizationRegistry.getPromise(languageId); if (tokenizationSupportPromise) { // A tokenizer will be registered soon return new Promise((resolve, reject) => { @@ -95,7 +95,7 @@ export class Colorizer { timeout.dispose(); timeout = null; } - const tokenizationSupport = TokenizationRegistry.get(language!); + const tokenizationSupport = TokenizationRegistry.get(languageId!); if (tokenizationSupport) { _colorize(lines, tabSize, tokenizationSupport, languageIdCodec).then(resolve, reject); return; @@ -107,7 +107,7 @@ export class Colorizer { timeout = new TimeoutTimer(); timeout.cancelAndSet(execute, 500); listener = TokenizationRegistry.onDidChange((e) => { - if (e.changedLanguages.indexOf(language!) >= 0) { + if (e.changedLanguages.indexOf(languageId!) >= 0) { execute(); } }); diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index a56d548d17d6d..f54d5bfc75baf 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -744,13 +744,14 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } const computeNewStateForEmbeddedMode = (enteringEmbeddedMode: string) => { - // substitute language alias to known modes to support syntax highlighting - let enteringEmbeddedLanguageId = this._languageService.getLanguageIdForLanguageName(enteringEmbeddedMode); - if (enteringEmbeddedLanguageId) { - enteringEmbeddedMode = enteringEmbeddedLanguageId; - } + // support language names, mime types, and language ids + const languageId = ( + this._languageService.getLanguageIdForLanguageName(enteringEmbeddedMode) + || this._languageService.getLanguageIdForMimeType(enteringEmbeddedMode) + || enteringEmbeddedMode + ); - const embeddedModeData = this._getNestedEmbeddedModeData(enteringEmbeddedMode); + const embeddedModeData = this._getNestedEmbeddedModeData(languageId); if (pos < lineLength) { // there is content from the embedded mode on this line @@ -846,44 +847,24 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { return MonarchLineStateFactory.create(stack, embeddedModeData); } - private _getNestedEmbeddedModeData(mimetypeOrModeId: string): EmbeddedModeData { - let nestedModeId = this._locateMode(mimetypeOrModeId); - if (nestedModeId) { - let tokenizationSupport = modes.TokenizationRegistry.get(nestedModeId); - if (tokenizationSupport) { - return new EmbeddedModeData(nestedModeId, tokenizationSupport.getInitialState()); - } - } - - return new EmbeddedModeData(nestedModeId || NULL_MODE_ID, NULL_STATE); - } - - private _locateMode(mimetypeOrModeId: string): string | null { - if (!mimetypeOrModeId) { - return null; + private _getNestedEmbeddedModeData(languageId: string): EmbeddedModeData { + if (!this._languageService.isRegisteredLanguageId(languageId)) { + return new EmbeddedModeData(languageId, NULL_STATE); } - const isRegisteredLanguageId = this._languageService.isRegisteredLanguageId(mimetypeOrModeId); - const isRegisteredMimeType = this._languageService.isRegisteredMimeType(mimetypeOrModeId); - if (!isRegisteredLanguageId && !isRegisteredMimeType) { - return null; - } - - if (mimetypeOrModeId === this._languageId) { - // embedding myself... - return mimetypeOrModeId; - } - - const languageId = this._languageService.getModeId(mimetypeOrModeId); - if (languageId) { + if (languageId !== this._languageId) { // Fire mode loading event this._languageService.triggerMode(languageId); this._embeddedModes[languageId] = true; } - return languageId; - } + const tokenizationSupport = modes.TokenizationRegistry.get(languageId); + if (tokenizationSupport) { + return new EmbeddedModeData(languageId, tokenizationSupport.getInitialState()); + } + return new EmbeddedModeData(languageId || NULL_MODE_ID, NULL_STATE); + } } /** diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index dbb27fe936b7b..7bf210492405f 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -168,7 +168,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { // Init language from text model // The language defined in the cell might not be supported in the editor so the text model might be using the default fallback // If so let's not modify the language - if (!(this._languageService.getModeId(this.language) === null && (this._textModel.getLanguageId() === 'plaintext' || this._textModel.getLanguageId() === 'jupyter'))) { + if (!(this._languageService.isRegisteredLanguageId(this.language) === false && (this._textModel.getLanguageId() === 'plaintext' || this._textModel.getLanguageId() === 'jupyter'))) { this.language = this._textModel.getLanguageId(); } From 327c71fdef2cb910c2a0dc545dccd81e406fc94f Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 8 Dec 2021 15:58:20 +0100 Subject: [PATCH 0438/2210] Allow duplication of task shell args Fixes #136459 --- .../tasks/browser/terminalTaskSystem.ts | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index f91db29425927..dba4a4d46c9ea 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1128,13 +1128,9 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { toAdd.push('-c'); } } - toAdd.forEach(element => { - if (!shellArgs.some(arg => arg.toLowerCase() === element)) { - shellArgs.push(element); - } - }); - shellArgs.push(commandLine); - shellLaunchConfig.args = windowsShellArgs ? shellArgs.join(' ') : shellArgs; + const combinedShellArgs = this.addAllArgument(toAdd, shellArgs); + combinedShellArgs.push(commandLine); + shellLaunchConfig.args = windowsShellArgs ? combinedShellArgs.join(' ') : combinedShellArgs; if (task.command.presentation && task.command.presentation.echo) { if (needsFolderQualification && workspaceFolder) { shellLaunchConfig.initialText = `\x1b[1m> Executing task in folder ${workspaceFolder.name}: ${commandLine} <\x1b[0m\n`; @@ -1196,6 +1192,24 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return shellLaunchConfig; } + private addAllArgument(shellCommandArgs: string[], configuredShellArgs: string[]): string[] { + const combinedShellArgs: string[] = Objects.deepClone(configuredShellArgs); + shellCommandArgs.forEach(element => { + const shouldAddShellCommandArg = configuredShellArgs.every((arg, index) => { + if ((arg.toLowerCase() === element) && (configuredShellArgs.length > index + 1)) { + // We can still add the argument, but only if not all of the following arguments begin with "-". + return !configuredShellArgs.slice(index + 1).every(testArg => testArg.startsWith('-')); + } else { + return arg.toLowerCase() !== element; + } + }); + if (shouldAddShellCommandArg) { + combinedShellArgs.push(element); + } + }); + return combinedShellArgs; + } + private async doCreateTerminal(group: string | undefined, launchConfigs: IShellLaunchConfig): Promise { if (group) { // Try to find an existing terminal to split. From 77e1ce431a9eff0b9430252e2ebd3aa6157e2157 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 8 Dec 2021 16:03:45 +0100 Subject: [PATCH 0439/2210] Fix terminal queueing in tasks (#138654) Fixes #134431 --- .../workbench/contrib/tasks/browser/terminalTaskSystem.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index dba4a4d46c9ea..9cf75dacca814 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1327,10 +1327,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return [terminalToReuse.terminal, commandExecutable, undefined]; } - await this.terminalCreationQueue; - const createTerminalPromise = this.doCreateTerminal(group, launchConfigs); - this.terminalCreationQueue = createTerminalPromise; - const result: ITerminalInstance = await createTerminalPromise; + this.terminalCreationQueue = this.terminalCreationQueue.then(() => this.doCreateTerminal(group, launchConfigs!)); + const result: ITerminalInstance = (await this.terminalCreationQueue)!; const terminalKey = result.instanceId.toString(); result.onDisposed((terminal) => { From d6681dfb536753d92a6fc6f0cf947ceb62dc8a81 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 8 Dec 2021 16:29:32 +0100 Subject: [PATCH 0440/2210] missing parens related to #138605 --- src/vs/workbench/contrib/scm/common/scmService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 42a1b1d3a6b09..1db24655ca5db 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -95,7 +95,7 @@ class SCMInput implements ISCMInput { const history = JSON.parse(storageService.get(key, StorageScope.GLOBAL, '[]')); if (Array.isArray(history)) { - if (history.length === 0 || history.length === 1 && history[0] === '') { + if (history.length === 0 || (history.length === 1 && history[0] === '')) { // remove empty histories storageService.remove(key, StorageScope.GLOBAL); } else { From 8e64d75b24a391b71d95386ea19c58a919124a9d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Dec 2021 16:31:32 +0100 Subject: [PATCH 0441/2210] check if model is dirty --- .../configuration/common/configurationEditingService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 47106e1e7489a..ff704ab4edb44 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -181,7 +181,8 @@ export class ConfigurationEditingService { const reference = await this.resolveModelReference(resource); try { const formattingOptions = this.getFormattingOptions(reference.object.textEditorModel); - if (this.uriIdentityService.extUri.isEqual(resource, this.environmentService.settingsResource)) { + if (!this.textFileService.isDirty(resource) /* go through text model save if the model is dirty */ + && this.uriIdentityService.extUri.isEqual(resource, this.environmentService.settingsResource)) { await this.userConfigurationFileService.updateSettings({ path: operation.jsonPath, value: operation.value }, formattingOptions); } else { await this.updateConfiguration(operation, reference.object.textEditorModel, formattingOptions); From dddea4442c30183926dac7b2268b1974c2f91fa8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 16:45:24 +0100 Subject: [PATCH 0442/2210] Symbol reordering. --- src/vs/editor/common/controller/cursor.ts | 203 +++++++++--------- .../controller/cursorColumnSelection.ts | 18 +- .../editor/common/controller/cursorCommon.ts | 160 +++++++------- 3 files changed, 191 insertions(+), 190 deletions(-) diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 4640244964ddf..3d5a859aedcd5 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -21,104 +21,6 @@ import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; import { CursorStateChangedEvent, ViewModelEventsCollector } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; -/** - * A snapshot of the cursor and the model state - */ -export class CursorModelState { - - public readonly modelVersionId: number; - public readonly cursorState: CursorState[]; - - constructor(model: ITextModel, cursor: CursorsController) { - this.modelVersionId = model.getVersionId(); - this.cursorState = cursor.getCursorStates(); - } - - public equals(other: CursorModelState | null): boolean { - if (!other) { - return false; - } - if (this.modelVersionId !== other.modelVersionId) { - return false; - } - if (this.cursorState.length !== other.cursorState.length) { - return false; - } - for (let i = 0, len = this.cursorState.length; i < len; i++) { - if (!this.cursorState[i].equals(other.cursorState[i])) { - return false; - } - } - return true; - } -} - -class AutoClosedAction { - - public static getAllAutoClosedCharacters(autoClosedActions: AutoClosedAction[]): Range[] { - let autoClosedCharacters: Range[] = []; - for (const autoClosedAction of autoClosedActions) { - autoClosedCharacters = autoClosedCharacters.concat(autoClosedAction.getAutoClosedCharactersRanges()); - } - return autoClosedCharacters; - } - - private readonly _model: ITextModel; - - private _autoClosedCharactersDecorations: string[]; - private _autoClosedEnclosingDecorations: string[]; - - constructor(model: ITextModel, autoClosedCharactersDecorations: string[], autoClosedEnclosingDecorations: string[]) { - this._model = model; - this._autoClosedCharactersDecorations = autoClosedCharactersDecorations; - this._autoClosedEnclosingDecorations = autoClosedEnclosingDecorations; - } - - public dispose(): void { - this._autoClosedCharactersDecorations = this._model.deltaDecorations(this._autoClosedCharactersDecorations, []); - this._autoClosedEnclosingDecorations = this._model.deltaDecorations(this._autoClosedEnclosingDecorations, []); - } - - public getAutoClosedCharactersRanges(): Range[] { - let result: Range[] = []; - for (let i = 0; i < this._autoClosedCharactersDecorations.length; i++) { - const decorationRange = this._model.getDecorationRange(this._autoClosedCharactersDecorations[i]); - if (decorationRange) { - result.push(decorationRange); - } - } - return result; - } - - public isValid(selections: Range[]): boolean { - let enclosingRanges: Range[] = []; - for (let i = 0; i < this._autoClosedEnclosingDecorations.length; i++) { - const decorationRange = this._model.getDecorationRange(this._autoClosedEnclosingDecorations[i]); - if (decorationRange) { - enclosingRanges.push(decorationRange); - if (decorationRange.startLineNumber !== decorationRange.endLineNumber) { - // Stop tracking if the range becomes multiline... - return false; - } - } - } - enclosingRanges.sort(Range.compareRangesUsingStarts); - - selections.sort(Range.compareRangesUsingStarts); - - for (let i = 0; i < selections.length; i++) { - if (i >= enclosingRanges.length) { - return false; - } - if (!enclosingRanges[i].strictContainsRange(selections[i])) { - return false; - } - } - - return true; - } -} - export class CursorsController extends Disposable { public static readonly MAX_CURSOR_COUNT = 10000; @@ -221,7 +123,7 @@ export class CursorsController extends Disposable { reachedMaxCursorCount = true; } - const oldState = new CursorModelState(this._model, this); + const oldState = CursorModelState.from(this._model, this); this._cursors.setStates(states); this._cursors.normalize(); @@ -505,7 +407,7 @@ export class CursorsController extends Disposable { // ----- emitting events private _emitStateChangedIfNecessary(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, reason: CursorChangeReason, oldState: CursorModelState | null, reachedMaxCursorCount: boolean): boolean { - const newState = new CursorModelState(this._model, this); + const newState = CursorModelState.from(this._model, this); if (newState.equals(oldState)) { return false; } @@ -616,7 +518,7 @@ export class CursorsController extends Disposable { return; } - const oldState = new CursorModelState(this._model, this); + const oldState = CursorModelState.from(this._model, this); this._cursors.stopTrackingSelections(); this._isHandling = true; @@ -731,6 +633,105 @@ export class CursorsController extends Disposable { } } +/** + * A snapshot of the cursor and the model state + */ +class CursorModelState { + public static from(model: ITextModel, cursor: CursorsController): CursorModelState { + return new CursorModelState(model.getVersionId(), cursor.getCursorStates()); + } + + constructor( + public readonly modelVersionId: number, + public readonly cursorState: CursorState[], + ) { + } + + public equals(other: CursorModelState | null): boolean { + if (!other) { + return false; + } + if (this.modelVersionId !== other.modelVersionId) { + return false; + } + if (this.cursorState.length !== other.cursorState.length) { + return false; + } + for (let i = 0, len = this.cursorState.length; i < len; i++) { + if (!this.cursorState[i].equals(other.cursorState[i])) { + return false; + } + } + return true; + } +} + +class AutoClosedAction { + + public static getAllAutoClosedCharacters(autoClosedActions: AutoClosedAction[]): Range[] { + let autoClosedCharacters: Range[] = []; + for (const autoClosedAction of autoClosedActions) { + autoClosedCharacters = autoClosedCharacters.concat(autoClosedAction.getAutoClosedCharactersRanges()); + } + return autoClosedCharacters; + } + + private readonly _model: ITextModel; + + private _autoClosedCharactersDecorations: string[]; + private _autoClosedEnclosingDecorations: string[]; + + constructor(model: ITextModel, autoClosedCharactersDecorations: string[], autoClosedEnclosingDecorations: string[]) { + this._model = model; + this._autoClosedCharactersDecorations = autoClosedCharactersDecorations; + this._autoClosedEnclosingDecorations = autoClosedEnclosingDecorations; + } + + public dispose(): void { + this._autoClosedCharactersDecorations = this._model.deltaDecorations(this._autoClosedCharactersDecorations, []); + this._autoClosedEnclosingDecorations = this._model.deltaDecorations(this._autoClosedEnclosingDecorations, []); + } + + public getAutoClosedCharactersRanges(): Range[] { + let result: Range[] = []; + for (let i = 0; i < this._autoClosedCharactersDecorations.length; i++) { + const decorationRange = this._model.getDecorationRange(this._autoClosedCharactersDecorations[i]); + if (decorationRange) { + result.push(decorationRange); + } + } + return result; + } + + public isValid(selections: Range[]): boolean { + let enclosingRanges: Range[] = []; + for (let i = 0; i < this._autoClosedEnclosingDecorations.length; i++) { + const decorationRange = this._model.getDecorationRange(this._autoClosedEnclosingDecorations[i]); + if (decorationRange) { + enclosingRanges.push(decorationRange); + if (decorationRange.startLineNumber !== decorationRange.endLineNumber) { + // Stop tracking if the range becomes multiline... + return false; + } + } + } + enclosingRanges.sort(Range.compareRangesUsingStarts); + + selections.sort(Range.compareRangesUsingStarts); + + for (let i = 0; i < selections.length; i++) { + if (i >= enclosingRanges.length) { + return false; + } + if (!enclosingRanges[i].strictContainsRange(selections[i])) { + return false; + } + } + + return true; + } +} + interface IExecContext { readonly model: ITextModel; readonly selectionsBefore: Selection[]; diff --git a/src/vs/editor/common/controller/cursorColumnSelection.ts b/src/vs/editor/common/controller/cursorColumnSelection.ts index eba5ef776e82b..129dba529d84d 100644 --- a/src/vs/editor/common/controller/cursorColumnSelection.ts +++ b/src/vs/editor/common/controller/cursorColumnSelection.ts @@ -7,15 +7,6 @@ import { CursorColumns, CursorConfiguration, ICursorSimpleModel, SingleCursorSta import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -export interface IColumnSelectResult { - viewStates: SingleCursorState[]; - reversed: boolean; - fromLineNumber: number; - fromVisualColumn: number; - toLineNumber: number; - toVisualColumn: number; -} - export class ColumnSelection { public static columnSelect(config: CursorConfiguration, model: ICursorSimpleModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IColumnSelectResult { @@ -124,3 +115,12 @@ export class ColumnSelection { return this.columnSelect(config, model, prevColumnSelectData.fromViewLineNumber, prevColumnSelectData.fromViewVisualColumn, toViewLineNumber, prevColumnSelectData.toViewVisualColumn); } } + +export interface IColumnSelectResult { + viewStates: SingleCursorState[]; + reversed: boolean; + fromLineNumber: number; + fromVisualColumn: number; + toLineNumber: number; + toVisualColumn: number; +} diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index b2ce0c7282b72..db02db9c4bc0b 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -227,6 +227,86 @@ export interface ICursorSimpleModel { getLineIndentColumn(lineNumber: number): number; } +export class CursorContext { + _cursorContextBrand: void = undefined; + + public readonly model: ITextModel; + public readonly viewModel: ICursorSimpleModel; + public readonly coordinatesConverter: ICoordinatesConverter; + public readonly cursorConfig: CursorConfiguration; + + constructor(model: ITextModel, viewModel: ICursorSimpleModel, coordinatesConverter: ICoordinatesConverter, cursorConfig: CursorConfiguration) { + this.model = model; + this.viewModel = viewModel; + this.coordinatesConverter = coordinatesConverter; + this.cursorConfig = cursorConfig; + } +} + +export type PartialCursorState = CursorState | PartialModelCursorState | PartialViewCursorState; + +export class CursorState { + _cursorStateBrand: void = undefined; + + public static fromModelState(modelState: SingleCursorState): PartialModelCursorState { + return new PartialModelCursorState(modelState); + } + + public static fromViewState(viewState: SingleCursorState): PartialViewCursorState { + return new PartialViewCursorState(viewState); + } + + public static fromModelSelection(modelSelection: ISelection): PartialModelCursorState { + const selection = Selection.liftSelection(modelSelection); + const modelState = new SingleCursorState( + Range.fromPositions(selection.getSelectionStart()), + 0, + selection.getPosition(), 0 + ); + return CursorState.fromModelState(modelState); + } + + public static fromModelSelections(modelSelections: readonly ISelection[]): PartialModelCursorState[] { + let states: PartialModelCursorState[] = []; + for (let i = 0, len = modelSelections.length; i < len; i++) { + states[i] = this.fromModelSelection(modelSelections[i]); + } + return states; + } + + readonly modelState: SingleCursorState; + readonly viewState: SingleCursorState; + + constructor(modelState: SingleCursorState, viewState: SingleCursorState) { + this.modelState = modelState; + this.viewState = viewState; + } + + public equals(other: CursorState): boolean { + return (this.viewState.equals(other.viewState) && this.modelState.equals(other.modelState)); + } +} + +export class PartialModelCursorState { + readonly modelState: SingleCursorState; + readonly viewState: null; + + constructor(modelState: SingleCursorState) { + this.modelState = modelState; + this.viewState = null; + } +} + +export class PartialViewCursorState { + readonly modelState: null; + readonly viewState: SingleCursorState; + + constructor(viewState: SingleCursorState) { + this.modelState = null; + this.viewState = viewState; + } +} + /** * Represents the cursor state on either the model or on the view model. */ @@ -295,86 +375,6 @@ export class SingleCursorState { } } -export class CursorContext { - _cursorContextBrand: void = undefined; - - public readonly model: ITextModel; - public readonly viewModel: ICursorSimpleModel; - public readonly coordinatesConverter: ICoordinatesConverter; - public readonly cursorConfig: CursorConfiguration; - - constructor(model: ITextModel, viewModel: ICursorSimpleModel, coordinatesConverter: ICoordinatesConverter, cursorConfig: CursorConfiguration) { - this.model = model; - this.viewModel = viewModel; - this.coordinatesConverter = coordinatesConverter; - this.cursorConfig = cursorConfig; - } -} - -export class PartialModelCursorState { - readonly modelState: SingleCursorState; - readonly viewState: null; - - constructor(modelState: SingleCursorState) { - this.modelState = modelState; - this.viewState = null; - } -} - -export class PartialViewCursorState { - readonly modelState: null; - readonly viewState: SingleCursorState; - - constructor(viewState: SingleCursorState) { - this.modelState = null; - this.viewState = viewState; - } -} - -export type PartialCursorState = CursorState | PartialModelCursorState | PartialViewCursorState; - -export class CursorState { - _cursorStateBrand: void = undefined; - - public static fromModelState(modelState: SingleCursorState): PartialModelCursorState { - return new PartialModelCursorState(modelState); - } - - public static fromViewState(viewState: SingleCursorState): PartialViewCursorState { - return new PartialViewCursorState(viewState); - } - - public static fromModelSelection(modelSelection: ISelection): PartialModelCursorState { - const selection = Selection.liftSelection(modelSelection); - const modelState = new SingleCursorState( - Range.fromPositions(selection.getSelectionStart()), - 0, - selection.getPosition(), 0 - ); - return CursorState.fromModelState(modelState); - } - - public static fromModelSelections(modelSelections: readonly ISelection[]): PartialModelCursorState[] { - let states: PartialModelCursorState[] = []; - for (let i = 0, len = modelSelections.length; i < len; i++) { - states[i] = this.fromModelSelection(modelSelections[i]); - } - return states; - } - - readonly modelState: SingleCursorState; - readonly viewState: SingleCursorState; - - constructor(modelState: SingleCursorState, viewState: SingleCursorState) { - this.modelState = modelState; - this.viewState = viewState; - } - - public equals(other: CursorState): boolean { - return (this.viewState.equals(other.viewState) && this.modelState.equals(other.modelState)); - } -} - export class EditOperationResult { _editOperationResultBrand: void = undefined; From 5df48870b7bbfaf4ee705485d078af71b5a2c910 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 15:46:06 +0100 Subject: [PATCH 0443/2210] Renames --- .../editor/common/services/getIconClasses.ts | 2 +- .../editor/common/services/languageService.ts | 2 +- .../common/services/languageServiceImpl.ts | 6 ++--- .../common/services/languagesRegistry.ts | 2 +- .../textResourceConfigurationServiceImpl.ts | 2 +- .../link/goToDefinitionAtPosition.ts | 2 +- .../textResourceConfigurationService.test.ts | 2 +- .../browser/parts/editor/editorStatus.ts | 4 +-- .../parts/editor/textResourceEditor.ts | 26 +++++++++---------- src/vs/workbench/common/resources.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 2 +- .../browser/themes.test.contribution.ts | 2 +- .../languageDetectionWorkerServiceImpl.ts | 2 +- 13 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index 0d570c27b9f61..d54c82f9f3349 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -95,7 +95,7 @@ function detectModeId(modelService: IModelService, languageService: ILanguageSer } // otherwise fallback to path based detection - return languageService.getModeIdByFilepathOrFirstLine(resource); + return languageService.getLanguageIdByFilepathOrFirstLine(resource); } export function cssEscape(str: string): string { diff --git a/src/vs/editor/common/services/languageService.ts b/src/vs/editor/common/services/languageService.ts index f800fb8fdfd88..43af74b9c0f30 100644 --- a/src/vs/editor/common/services/languageService.ts +++ b/src/vs/editor/common/services/languageService.ts @@ -44,7 +44,7 @@ export interface ILanguageService { getLanguageName(languageId: string): string | null; getLanguageIdForLanguageName(languageName: string): string | null; getLanguageIdForMimeType(mimeType: string): string | null; - getModeIdByFilepathOrFirstLine(resource: URI, firstLine?: string): string | null; + getLanguageIdByFilepathOrFirstLine(resource: URI, firstLine?: string): string | null; validateLanguageId(languageId: string): string | null; getConfigurationFiles(languageId: string): URI[]; diff --git a/src/vs/editor/common/services/languageServiceImpl.ts b/src/vs/editor/common/services/languageServiceImpl.ts index 60f3c706d071a..6370bce874fdf 100644 --- a/src/vs/editor/common/services/languageServiceImpl.ts +++ b/src/vs/editor/common/services/languageServiceImpl.ts @@ -112,8 +112,8 @@ export class LanguageService extends Disposable implements ILanguageService { return this._registry.getLanguageIdForMimeType(mimeType); } - public getModeIdByFilepathOrFirstLine(resource: URI | null, firstLine?: string): string | null { - const modeIds = this._registry.getModeIdsFromFilepathOrFirstLine(resource, firstLine); + public getLanguageIdByFilepathOrFirstLine(resource: URI | null, firstLine?: string): string | null { + const modeIds = this._registry.getLanguageIdByFilepathOrFirstLine(resource, firstLine); return firstOrDefault(modeIds, null); } @@ -148,7 +148,7 @@ export class LanguageService extends Disposable implements ILanguageService { public createByFilepathOrFirstLine(resource: URI | null, firstLine?: string): ILanguageSelection { return new LanguageSelection(this.onLanguagesMaybeChanged, () => { - const languageId = this.getModeIdByFilepathOrFirstLine(resource, firstLine); + const languageId = this.getLanguageIdByFilepathOrFirstLine(resource, firstLine); return this._createModeAndGetLanguageIdentifier(languageId); }); } diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 668f0c7e0993c..554acb2758f33 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -348,7 +348,7 @@ export class LanguagesRegistry extends Disposable { return null; } - public getModeIdsFromFilepathOrFirstLine(resource: URI | null, firstLine?: string): string[] { + public getLanguageIdByFilepathOrFirstLine(resource: URI | null, firstLine?: string): string[] { if (!resource && !firstLine) { return []; } diff --git a/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts b/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts index b5fcd62bb9c18..feb6c12cd9402 100644 --- a/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts +++ b/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts @@ -111,7 +111,7 @@ export class TextResourceConfigurationService extends Disposable implements ITex if (model) { return position ? model.getLanguageIdAtPosition(position.lineNumber, position.column) : model.getLanguageId(); } - return this.languageService.getModeIdByFilepathOrFirstLine(resource); + return this.languageService.getLanguageIdByFilepathOrFirstLine(resource); } private toResourceConfigurationChangeEvent(configurationChangeEvent: IConfigurationChangeEvent): ITextResourceConfigurationChangeEvent { diff --git a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index 2b1b37fd19d1f..116305d9c8a41 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -204,7 +204,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri wordRange = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); } - const languageId = this.languageService.getModeIdByFilepathOrFirstLine(textEditorModel.uri); + const languageId = this.languageService.getLanguageIdByFilepathOrFirstLine(textEditorModel.uri); this.addDecoration( wordRange, new MarkdownString().appendCodeblock(languageId ? languageId : '', previewValue) diff --git a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts index 91f82ab489780..f4e6182b3d2e1 100644 --- a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts +++ b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts @@ -32,7 +32,7 @@ suite('TextResourceConfigurationService - Update', () => { setup(() => { const instantiationService = new TestInstantiationService(); instantiationService.stub(IModelService, >{ getModel() { return null; } }); - instantiationService.stub(ILanguageService, >{ getModeIdByFilepathOrFirstLine() { return language; } }); + instantiationService.stub(ILanguageService, >{ getLanguageIdByFilepathOrFirstLine() { return language; } }); instantiationService.stub(IConfigurationService, configurationService); testObject = instantiationService.createInstance(TextResourceConfigurationService); }); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index e731ae64b910c..5da4fd7598aa0 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1201,7 +1201,7 @@ export class ChangeModeAction extends Action { const resource = EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); if (resource) { // Detect languages since we are in an untitled file - let languageId: string | undefined = withNullAsUndefined(this.languageService.getModeIdByFilepathOrFirstLine(resource, textModel.getLineContent(1))); + let languageId: string | undefined = withNullAsUndefined(this.languageService.getLanguageIdByFilepathOrFirstLine(resource, textModel.getLineContent(1))); if (!languageId) { detectedLanguage = await this.languageDetectionService.detectLanguage(resource); languageId = detectedLanguage; @@ -1247,7 +1247,7 @@ export class ChangeModeAction extends Action { private configureFileAssociation(resource: URI): void { const extension = extname(resource); const base = basename(resource); - const currentAssociation = this.languageService.getModeIdByFilepathOrFirstLine(URI.file(base)); + const currentAssociation = this.languageService.getLanguageIdByFilepathOrFirstLine(URI.file(base)); const languages = this.languageService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 7ce3a573889d3..c852fca8abc3a 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -181,29 +181,29 @@ export class TextResourceEditor extends AbstractTextResourceEditor { return; // require a live model } - const currentMode = textModel.getLanguageId(); - if (currentMode !== PLAINTEXT_MODE_ID) { - return; // require current mode to be unspecific + const currentLanguageId = textModel.getLanguageId(); + if (currentLanguageId !== PLAINTEXT_MODE_ID) { + return; // require current languageId to be unspecific } - let candidateMode: string | undefined = undefined; + let candidateLanguageId: string | undefined = undefined; - // A mode is provided via the paste event so text was copied using - // VSCode. As such we trust this mode and use it if specific + // A languageId is provided via the paste event so text was copied using + // VSCode. As such we trust this languageId and use it if specific if (e.languageId) { - candidateMode = e.languageId; + candidateLanguageId = e.languageId; } - // A mode was not provided, so the data comes from outside VSCode - // We can still try to guess a good mode from the first line if + // A languageId was not provided, so the data comes from outside VSCode + // We can still try to guess a good languageId from the first line if // the paste changed the first line else { - candidateMode = withNullAsUndefined(this.languageService.getModeIdByFilepathOrFirstLine(textModel.uri, textModel.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT))); + candidateLanguageId = withNullAsUndefined(this.languageService.getLanguageIdByFilepathOrFirstLine(textModel.uri, textModel.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT))); } - // Finally apply mode to model if specified - if (candidateMode !== PLAINTEXT_MODE_ID) { - this.modelService.setMode(textModel, this.languageService.create(candidateMode)); + // Finally apply languageId to model if specified + if (candidateLanguageId !== PLAINTEXT_MODE_ID) { + this.modelService.setMode(textModel, this.languageService.create(candidateLanguageId)); } } } diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 806b429388634..1e52e04c5a871 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -89,7 +89,7 @@ export class ResourceContextKey implements IContextKey { this._langIdKey.set(null); return; } - const langId = this._modelService.getModel(value)?.getLanguageId() ?? this._languageService.getModeIdByFilepathOrFirstLine(value); + const langId = this._modelService.getModel(value)?.getLanguageId() ?? this._languageService.getLanguageIdByFilepathOrFirstLine(value); this._langIdKey.set(langId); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 412e9a93142c6..d69af8409e30b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -805,7 +805,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const keywords = lookup[ext] || []; // Get mode name - const languageId = this.languageService.getModeIdByFilepathOrFirstLine(URI.file(`.${ext}`)); + const languageId = this.languageService.getLanguageIdByFilepathOrFirstLine(URI.file(`.${ext}`)); const languageName = languageId && this.languageService.getLanguageName(languageId); const languageTag = languageName ? ` tag:"${languageName}"` : ''; diff --git a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts index d877b697925fd..d1713161ec431 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts @@ -216,7 +216,7 @@ class Snapper { } public captureSyntaxTokens(fileName: string, content: string): Promise { - const languageId = this.languageService.getModeIdByFilepathOrFirstLine(URI.file(fileName)); + const languageId = this.languageService.getLanguageIdByFilepathOrFirstLine(URI.file(fileName)); return this.textMateService.createGrammar(languageId!).then((grammar) => { if (!grammar) { return []; diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index b3676c3b3c7c5..811f5654182b1 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -59,7 +59,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet if (!language) { return undefined; } - return this._languageService.getModeIdByFilepathOrFirstLine(URI.file(`file.${language}`)) ?? undefined; + return this._languageService.getLanguageIdByFilepathOrFirstLine(URI.file(`file.${language}`)) ?? undefined; } async detectLanguage(resource: URI): Promise { From cb3bc8a60e6fec5db31620ca6ad879d2346e6161 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 16:50:42 +0100 Subject: [PATCH 0444/2210] Split `ILanguageService.create` into `createById` and `createByMimeType` --- .../editor/common/services/languageService.ts | 21 +++++++++++++----- .../common/services/languageServiceImpl.ts | 22 +++++++++---------- .../common/services/languagesRegistry.ts | 12 +++++++--- .../browser/standaloneCodeEditor.ts | 11 +++++----- .../standalone/browser/standaloneEditor.ts | 8 ++++--- .../test/common/services/modelService.test.ts | 4 ++-- .../api/browser/mainThreadLanguages.ts | 2 +- .../browser/parts/editor/editorStatus.ts | 5 +++-- .../parts/editor/textResourceEditor.ts | 2 +- .../common/editor/textEditorModel.ts | 6 ++--- .../browser/preview/bulkEditPreview.ts | 2 +- .../debug/common/debugContentProvider.ts | 7 +++--- .../workbench/contrib/files/common/files.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 2 +- .../contrib/codeRenderer/codeRenderer.ts | 2 +- .../notebook/browser/diff/diffComponents.ts | 6 ++--- .../notebook/browser/notebook.contribution.ts | 8 +++---- .../common/model/notebookCellTextModel.ts | 2 +- .../notebookEditorKernelManager.test.ts | 4 ++-- .../browser/notebookKernelService.test.ts | 4 ++-- .../output/common/outputChannelModel.ts | 10 ++++----- .../common/outputChannelModelService.ts | 4 ++-- .../performance/browser/perfviewEditor.ts | 2 +- .../common/preferencesContribution.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 2 +- .../contrib/search/browser/replaceService.ts | 2 +- .../searchEditor/browser/searchEditorModel.ts | 10 ++++----- .../testing/common/testingContentProvider.ts | 2 +- .../userDataSync/browser/userDataSync.ts | 2 +- .../common/walkThroughContentProvider.ts | 2 +- .../preferences/browser/preferencesService.ts | 6 ++--- .../browser/textModelResolverService.test.ts | 4 ++-- .../parts/editor/editorDiffModel.test.ts | 2 +- .../editor/textResourceEditorInput.test.ts | 10 ++++----- 34 files changed, 105 insertions(+), 87 deletions(-) diff --git a/src/vs/editor/common/services/languageService.ts b/src/vs/editor/common/services/languageService.ts index 43af74b9c0f30..2ba0b73a9b8f3 100644 --- a/src/vs/editor/common/services/languageService.ts +++ b/src/vs/editor/common/services/languageService.ts @@ -38,19 +38,28 @@ export interface ILanguageService { isRegisteredLanguageId(languageId: string): boolean; getRegisteredLanguageIds(): string[]; getRegisteredLanguageNames(): string[]; - getExtensions(alias: string): string[]; - getFilenames(alias: string): string[]; + getExtensions(alias: string): string[]; // TODO + getFilenames(alias: string): string[]; // TODO getMimeTypeForLanguageId(languageId: string): string | null; getLanguageName(languageId: string): string | null; - getLanguageIdForLanguageName(languageName: string): string | null; - getLanguageIdForMimeType(mimeType: string): string | null; + getLanguageIdForLanguageName(languageName: string): string | null; // TODO + getLanguageIdForMimeType(mimeType: string | null | undefined): string | null; getLanguageIdByFilepathOrFirstLine(resource: URI, firstLine?: string): string | null; validateLanguageId(languageId: string): string | null; getConfigurationFiles(languageId: string): URI[]; // --- instantiation - create(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): ILanguageSelection; - createByLanguageName(languageName: string): ILanguageSelection; + /** + * Will fall back to 'plaintext' if `languageId` is unknown. + */ + createById(languageId: string | null | undefined): ILanguageSelection; + /** + * Will fall back to 'plaintext' if `mimeType` is unknown. + */ + createByMimeType(mimeType: string | null | undefined): ILanguageSelection; + /** + * Will fall back to 'plaintext' if the `languageId` cannot be determined. + */ createByFilepathOrFirstLine(resource: URI | null, firstLine?: string): ILanguageSelection; triggerMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): void; diff --git a/src/vs/editor/common/services/languageServiceImpl.ts b/src/vs/editor/common/services/languageServiceImpl.ts index 6370bce874fdf..cb8fffaec439f 100644 --- a/src/vs/editor/common/services/languageServiceImpl.ts +++ b/src/vs/editor/common/services/languageServiceImpl.ts @@ -11,6 +11,7 @@ import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; import { ILanguageSelection, ILanguageService } from 'vs/editor/common/services/languageService'; import { firstOrDefault } from 'vs/base/common/arrays'; import { ILanguageIdCodec } from 'vs/editor/common/modes'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; class LanguageSelection implements ILanguageSelection { @@ -76,7 +77,7 @@ export class LanguageService extends Disposable implements ILanguageService { super.dispose(); } - public isRegisteredLanguageId(languageId: string): boolean { + public isRegisteredLanguageId(languageId: string | null | undefined): boolean { return this._registry.isRegisteredLanguageId(languageId); } @@ -108,7 +109,7 @@ export class LanguageService extends Disposable implements ILanguageService { return this._registry.getLanguageIdForLanguageName(alias); } - public getLanguageIdForMimeType(mimeType: string): string | null { + public getLanguageIdForMimeType(mimeType: string | null | undefined): string | null { return this._registry.getLanguageIdForMimeType(mimeType); } @@ -132,16 +133,17 @@ export class LanguageService extends Disposable implements ILanguageService { // --- instantiation - public create(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): ILanguageSelection { + public createById(languageId: string | null | undefined): ILanguageSelection { return new LanguageSelection(this.onLanguagesMaybeChanged, () => { - const languageId = this.getModeId(commaSeparatedMimetypesOrCommaSeparatedIds); - return this._createModeAndGetLanguageIdentifier(languageId); + const validLanguageId = (languageId && this.isRegisteredLanguageId(languageId) ? languageId : PLAINTEXT_MODE_ID); + this._getOrCreateMode(validLanguageId); + return validLanguageId; }); } - public createByLanguageName(languageName: string): ILanguageSelection { + public createByMimeType(mimeType: string | null | undefined): ILanguageSelection { return new LanguageSelection(this.onLanguagesMaybeChanged, () => { - const languageId = this._getModeIdByLanguageName(languageName); + const languageId = this.getLanguageIdForMimeType(mimeType); return this._createModeAndGetLanguageIdentifier(languageId); }); } @@ -153,7 +155,7 @@ export class LanguageService extends Disposable implements ILanguageService { }); } - private _createModeAndGetLanguageIdentifier(languageId: string | null): string { + private _createModeAndGetLanguageIdentifier(languageId: string | null | undefined): string { // Fall back to plain text if no mode was found const validLanguageId = this.validateLanguageId(languageId || 'plaintext') || NULL_MODE_ID; this._getOrCreateMode(validLanguageId); @@ -166,10 +168,6 @@ export class LanguageService extends Disposable implements ILanguageService { this._getOrCreateMode(languageId || 'plaintext'); } - private _getModeIdByLanguageName(languageName: string): string | null { - return this._registry.getModeIdFromLanguageName(languageName); - } - private _getOrCreateMode(languageId: string): void { if (!this._encounteredLanguages.has(languageId)) { this._encounteredLanguages.add(languageId); diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 554acb2758f33..18e649752ab66 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -257,7 +257,10 @@ export class LanguagesRegistry extends Disposable { } } - public isRegisteredLanguageId(languageId: string): boolean { + public isRegisteredLanguageId(languageId: string | null | undefined): boolean { + if (!languageId) { + return false; + } return hasOwnProperty.call(this._languages, languageId); } @@ -283,7 +286,10 @@ export class LanguagesRegistry extends Disposable { return this._lowercaseNameMap[languageNameLower]; } - public getLanguageIdForMimeType(mimeType: string): string | null { + public getLanguageIdForMimeType(mimeType: string | null | undefined): string | null { + if (!mimeType) { + return null; + } if (hasOwnProperty.call(this._mimeTypesMap, mimeType)) { return this._mimeTypesMap[mimeType]; } @@ -326,7 +332,7 @@ export class LanguagesRegistry extends Disposable { ); } - public validateLanguageId(languageId: string | null): string | null { + public validateLanguageId(languageId: string | null | undefined): string | null { if (!languageId || languageId === NULL_MODE_ID) { return NULL_MODE_ID; } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 9ac0e9505a07b..7d806b8cb52fc 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -35,7 +35,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ILanguageSelection, ILanguageService } from 'vs/editor/common/services/languageService'; import { URI } from 'vs/base/common/uri'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; -import { Mimes } from 'vs/base/common/mime'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; /** * Description of an action contribution @@ -437,7 +437,8 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon let model: ITextModel | null; if (typeof _model === 'undefined') { - model = createTextModel(modelService, languageService, options.value || '', options.language || Mimes.text, undefined); + const languageId = languageService.getLanguageIdForMimeType(options.language) || options.language || PLAINTEXT_MODE_ID; + model = createTextModel(modelService, languageService, options.value || '', languageId, undefined); this._ownsModel = true; } else { model = _model; @@ -573,9 +574,9 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon /** * @internal */ -export function createTextModel(modelService: IModelService, languageService: ILanguageService, value: string, language: string | undefined, uri: URI | undefined): ITextModel { +export function createTextModel(modelService: IModelService, languageService: ILanguageService, value: string, languageId: string | undefined, uri: URI | undefined): ITextModel { value = value || ''; - if (!language) { + if (!languageId) { const firstLF = value.indexOf('\n'); let firstLine = value; if (firstLF !== -1) { @@ -583,7 +584,7 @@ export function createTextModel(modelService: IModelService, languageService: IL } return doCreateModel(modelService, value, languageService.createByFilepathOrFirstLine(uri || null, firstLine), uri); } - return doCreateModel(modelService, value, languageService.create(language), uri); + return doCreateModel(modelService, value, languageService.createById(languageId), uri); } /** diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 901fbd7e7b35a..45049b5739ed8 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -148,11 +148,13 @@ export function createDiffNavigator(diffEditor: IStandaloneDiffEditor, opts?: ID * You can specify the language that should be set for this model or let the language be inferred from the `uri`. */ export function createModel(value: string, language?: string, uri?: URI): ITextModel { + const languageService = StaticServices.languageService.get(); + const languageId = languageService.getLanguageIdForMimeType(language) || language; return createTextModel( StaticServices.modelService.get(), - StaticServices.languageService.get(), + languageService, value, - language, + languageId, uri ); } @@ -161,7 +163,7 @@ export function createModel(value: string, language?: string, uri?: URI): ITextM * Change the language for a model. */ export function setModelLanguage(model: ITextModel, languageId: string): void { - StaticServices.modelService.get().setMode(model, StaticServices.languageService.get().create(languageId)); + StaticServices.modelService.get().setMode(model, StaticServices.languageService.get().createById(languageId)); } /** diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 40efc85b13db3..04b368dd4bd22 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -469,7 +469,7 @@ suite('ModelSemanticColoring', () => { } })); - const textModel = disposables.add(modelService.createModel('Hello world', languageService.create('testMode'))); + const textModel = disposables.add(modelService.createModel('Hello world', languageService.createById('testMode'))); // wait for the provider to be called await inFirstCall.wait(); @@ -532,7 +532,7 @@ suite('ModelSemanticColoring', () => { return result; } - const textModel = modelService.createModel('Hello world 2', languageService.create('testMode2')); + const textModel = modelService.createModel('Hello world 2', languageService.createById('testMode2')); try { let result = await getDocumentSemanticTokens(textModel, null, null, CancellationToken.None); assert.ok(result, `We should have tokens (1)`); diff --git a/src/vs/workbench/api/browser/mainThreadLanguages.ts b/src/vs/workbench/api/browser/mainThreadLanguages.ts index 05d2e7c52ca66..bd20b8a413d83 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguages.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguages.ts @@ -57,7 +57,7 @@ export class MainThreadLanguages implements MainThreadLanguagesShape { const uri = URI.revive(resource); const ref = await this._resolverService.createModelReference(uri); try { - this._modelService.setMode(ref.object.textEditorModel, this._languageService.create(languageId)); + this._modelService.setMode(ref.object.textEditorModel, this._languageService.createById(languageId)); } finally { ref.dispose(); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 5da4fd7598aa0..675c528e624f0 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1207,12 +1207,13 @@ export class ChangeModeAction extends Action { languageId = detectedLanguage; } if (languageId) { - languageSelection = this.languageService.create(languageId); + languageSelection = this.languageService.createById(languageId); } } } } else { - languageSelection = this.languageService.createByLanguageName(pick.label); + const languageId = this.languageService.getLanguageIdForLanguageName(pick.label.toLowerCase()); + languageSelection = this.languageService.createById(languageId); if (resource) { // fire and forget to not slow things down diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index c852fca8abc3a..7f290a1417a9e 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -203,7 +203,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { // Finally apply languageId to model if specified if (candidateLanguageId !== PLAINTEXT_MODE_ID) { - this.modelService.setMode(textModel, this.languageService.create(candidateLanguageId)); + this.modelService.setMode(textModel, this.languageService.createById(candidateLanguageId)); } } } diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index 2855654dec317..cb218c1563eb3 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -95,7 +95,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel return; } - this.modelService.setMode(this.textEditorModel, this.languageService.create(mode)); + this.modelService.setMode(this.textEditorModel, this.languageService.createById(mode)); } getMode(): string | undefined { @@ -179,7 +179,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel } // otherwise take the preferred mode for granted - return languageService.create(preferredMode); + return languageService.createById(preferredMode); } /** @@ -197,7 +197,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel // mode (only if specific and changed) if (preferredMode && preferredMode !== PLAINTEXT_MODE_ID && this.textEditorModel.getLanguageId() !== preferredMode) { - this.modelService.setMode(this.textEditorModel, this.languageService.create(preferredMode)); + this.modelService.setMode(this.textEditorModel, this.languageService.createById(preferredMode)); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index aa908f8da870c..01e837e6e3ce2 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -416,7 +416,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { const sourceModel = ref.object.textEditorModel; model = this._modelService.createModel( createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), - this._languageService.create(sourceModel.getLanguageId()), + this._languageService.createById(sourceModel.getLanguageId()), previewUri ); ref.dispose(); diff --git a/src/vs/workbench/contrib/debug/common/debugContentProvider.ts b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts index 68e56e8a36a33..87d13cdbb4665 100644 --- a/src/vs/workbench/contrib/debug/common/debugContentProvider.ts +++ b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts @@ -5,7 +5,7 @@ import { URI as uri } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { guessMimeTypes, Mimes } from 'vs/base/common/mime'; +import { guessMimeTypes } from 'vs/base/common/mime'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILanguageService } from 'vs/editor/common/services/languageService'; @@ -17,6 +17,7 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; /** * Debug URI format @@ -94,7 +95,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC } const createErrModel = (errMsg?: string) => { this.debugService.sourceIsNotAvailable(resource); - const languageSelection = this.languageService.create(Mimes.text); + const languageSelection = this.languageService.createById(PLAINTEXT_MODE_ID); const message = errMsg ? localize('canNotResolveSourceWithError', "Could not load source '{0}': {1}.", resource.path, errMsg) : localize('canNotResolveSource', "Could not load source '{0}'.", resource.path); @@ -134,7 +135,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC } else { // create text model const mime = response.body.mimeType || guessMimeTypes(resource)[0]; - const languageSelection = this.languageService.create(mime); + const languageSelection = this.languageService.createByMimeType(mime); return this.modelService.createModel(response.body.content, languageSelection, resource); } } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 69a2b1885771e..4dbcc0967f534 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -198,7 +198,7 @@ export class TextFileContentProvider extends Disposable implements ITextModelCon let languageSelector: ILanguageSelection; if (textFileModel) { - languageSelector = this.languageService.create(textFileModel.getLanguageId()); + languageSelector = this.languageService.createById(textFileModel.getLanguageId()); } else { languageSelector = this.languageService.createByFilepathOrFirstLine(savedFileResource); } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index d9b7c2db8d33b..e4822942b42df 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -513,7 +513,7 @@ export class InteractiveEditor extends EditorPane { if (selectedOrSuggested) { const language = selectedOrSuggested.supportedLanguages[0]; - const newMode = language ? this.#languageService.create(language).languageId : PLAINTEXT_MODE_ID; + const newMode = language ? this.#languageService.createById(language).languageId : PLAINTEXT_MODE_ID; textModel.setMode(newMode); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts b/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts index 6ccc1520073ed..c93a2fb96ca9e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts @@ -60,7 +60,7 @@ abstract class CodeRendererContrib extends Disposable implements IOutputTransfor container.style.height = `${editorHeight + 8}px`; })); - const mode = this.languageService.create(languageId); + const mode = this.languageService.createById(languageId); const textModel = this.modelService.createModel(value, mode, undefined, false); editor.setModel(textModel); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index dcdc3d0f93558..930fd49395b07 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -596,7 +596,7 @@ abstract class AbstractElementRenderer extends Disposable { this.layout({ metadataHeight: true }); this._metadataEditorDisposeStore.add(this._metadataEditor); - const mode = this.languageService.create('jsonc'); + const mode = this.languageService.createById('jsonc'); const originalMetadataSource = getFormatedMetadataJSON(this.notebookEditor.textModel!, this.cell.type === 'insert' ? this.cell.modified!.metadata || {} @@ -630,7 +630,7 @@ abstract class AbstractElementRenderer extends Disposable { const originalOutputsSource = getFormatedOutputJSON(this.cell.original?.outputs || []); const modifiedOutputsSource = getFormatedOutputJSON(this.cell.modified?.outputs || []); if (originalOutputsSource !== modifiedOutputsSource) { - const mode = this.languageService.create('json'); + const mode = this.languageService.createById('json'); const originalModel = this.modelService.createModel(originalOutputsSource, mode, undefined, true); const modifiedModel = this.modelService.createModel(modifiedOutputsSource, mode, undefined, true); this._outputEditorDisposeStore.add(originalModel); @@ -690,7 +690,7 @@ abstract class AbstractElementRenderer extends Disposable { }, {}); this._outputEditorDisposeStore.add(this._outputEditor); - const mode = this.languageService.create('json'); + const mode = this.languageService.createById('json'); const originaloutputSource = getFormatedOutputJSON( this.notebookEditor.textModel!.transientOptions.transientOutputs ? [] diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index bb4f0d24802be..7e0325f9924f7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -275,7 +275,7 @@ class CellContentProvider implements ITextModelContentProvider { } }; const languageId = this._languageService.getLanguageIdForLanguageName(cell.language); - const languageSelection = languageId ? this._languageService.create(languageId) : (cell.cellKind === CellKind.Markup ? this._languageService.create('markdown') : this._languageService.createByFilepathOrFirstLine(resource, cell.textBuffer.getLineContent(1))); + const languageSelection = languageId ? this._languageService.createById(languageId) : (cell.cellKind === CellKind.Markup ? this._languageService.createById('markdown') : this._languageService.createByFilepathOrFirstLine(resource, cell.textBuffer.getLineContent(1))); result = this._modelService.createModel( bufferFactory, languageSelection, @@ -349,7 +349,7 @@ class CellInfoContentProvider { const ref = await this._notebookModelResolverService.resolve(data.notebook); let result: ITextModel | null = null; - const mode = this._languageService.create('json'); + const mode = this._languageService.createById('json'); for (const cell of ref.object.notebook.cells) { if (cell.handle === data.handle) { @@ -382,7 +382,7 @@ class CellInfoContentProvider { if (streamOutputData) { return { content: streamOutputData, - mode: this._languageService.create('plaintext') + mode: this._languageService.createById('plaintext') }; } @@ -396,7 +396,7 @@ class CellInfoContentProvider { }, cell: NotebookCellTextModel) { let result: { content: string, mode: ILanguageSelection } | undefined = undefined; - const mode = this._languageService.create('json'); + const mode = this._languageService.createById('json'); const op = cell.outputs.find(op => op.outputId === data.outputId); const streamOutputData = this.parseStreamOutput(op); if (streamOutputData) { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 7bf210492405f..25597ad2e36a3 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -92,7 +92,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { } if (this._textModel) { - const languageId = this._languageService.create(newLanguageId); + const languageId = this._languageService.createById(newLanguageId); this._textModel.setMode(languageId.languageId); } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorKernelManager.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorKernelManager.test.ts index bb337ccced1ef..31fc1461357a8 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorKernelManager.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorKernelManager.test.ts @@ -20,7 +20,7 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS import { mock } from 'vs/base/test/common/mock'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Mimes } from 'vs/base/common/mime'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { insertCellAtIndex } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; suite('NotebookEditorKernelManager', () => { @@ -157,6 +157,6 @@ class TestNotebookKernel implements INotebookKernel { } constructor(opts?: { languages: string[] }) { - this.supportedLanguages = opts?.languages ?? [Mimes.text]; + this.supportedLanguages = opts?.languages ?? [PLAINTEXT_MODE_ID]; } } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index 0d4f388d0e7fd..15173253d361b 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -15,7 +15,7 @@ import { mock } from 'vs/base/test/common/mock'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { Mimes } from 'vs/base/common/mime'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; suite('NotebookKernelService', () => { @@ -179,7 +179,7 @@ class TestNotebookKernel implements INotebookKernel { } constructor(opts?: { languages?: string[], label?: string, viewType?: string }) { - this.supportedLanguages = opts?.languages ?? [Mimes.text]; + this.supportedLanguages = opts?.languages ?? [PLAINTEXT_MODE_ID]; this.label = opts?.label ?? this.label; this.viewType = opts?.viewType ?? this.viewType; } diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 59a4f7934f566..99ea7c0aea6f7 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -106,7 +106,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel constructor( private readonly modelUri: URI, - private readonly mimeType: string, + private readonly mimeType: 'text/x-code-log-output' | 'text/x-code-output', private readonly file: URI, @IFileService private readonly fileService: IFileService, @IModelService private readonly modelService: IModelService, @@ -163,7 +163,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel if (this.model) { this.model.setValue(content); } else { - this.model = this.modelService.createModel(content, this.languageService.create(this.mimeType), this.modelUri); + this.model = this.modelService.createModel(content, this.languageService.createByMimeType(this.mimeType), this.modelUri); this.fileHandler.watch(this.etag); const disposable = this.model.onWillDispose(() => { this.cancelModelUpdate(); @@ -324,7 +324,7 @@ class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutpu constructor( id: string, modelUri: URI, - mimeType: string, + mimeType: 'text/x-code-log-output' | 'text/x-code-output', file: URI, @IFileService fileService: IFileService, @IModelService modelService: IModelService, @@ -371,7 +371,7 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh constructor( id: string, modelUri: URI, - mimeType: string, + mimeType: 'text/x-code-log-output' | 'text/x-code-output', outputDir: Promise, @IInstantiationService private readonly instantiationService: IInstantiationService, @IFileService private readonly fileService: IFileService, @@ -380,7 +380,7 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh this.outputChannelModel = this.createOutputChannelModel(id, modelUri, mimeType, outputDir); } - private async createOutputChannelModel(id: string, modelUri: URI, mimeType: string, outputDirPromise: Promise): Promise { + private async createOutputChannelModel(id: string, modelUri: URI, mimeType: 'text/x-code-log-output' | 'text/x-code-output', outputDirPromise: Promise): Promise { const outputDir = await outputDirPromise; const file = resources.joinPath(outputDir, `${id.replace(/[\\/:\*\?"<>\|]/g, '')}.log`); await this.fileService.createFile(file); diff --git a/src/vs/workbench/contrib/output/common/outputChannelModelService.ts b/src/vs/workbench/contrib/output/common/outputChannelModelService.ts index 8fede8ab062ca..e003de09770ba 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModelService.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModelService.ts @@ -17,7 +17,7 @@ export const IOutputChannelModelService = createDecorator { diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index 09eeb02a5a1c2..2b173447827da 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -122,7 +122,7 @@ export class PreferencesContribution implements IWorkbenchContribution { let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; if (schema) { const modelContent = JSON.stringify(schema); - const languageSelection = this.languageService.create('jsonc'); + const languageSelection = this.languageService.createById('jsonc'); const model = this.modelService.createModel(modelContent, languageSelection, uri); const disposables = new DisposableStore(); disposables.add(schemaRegistry.onDidChangeSchema(schemaUri => { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 2d1f8002d4d71..3e8f9a3a09084 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1649,7 +1649,7 @@ class SCMInputWidget extends Disposable { this.configurationService.updateValue('editor.wordBasedSuggestions', false, { resource: uri }, ConfigurationTarget.MEMORY); } - const textModel = this.modelService.getModel(uri) ?? this.modelService.createModel('', this.languageService.create('scminput'), uri); + const textModel = this.modelService.getModel(uri) ?? this.modelService.createModel('', this.languageService.createById('scminput'), uri); this.inputEditor.setModel(textModel); // Validation diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 2c2dd137cb83d..c24f6da0aba4c 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -71,7 +71,7 @@ class ReplacePreviewModel extends Disposable { const ref = this._register(await this.textModelResolverService.createModelReference(fileResource)); const sourceModel = ref.object.textEditorModel; const sourceModelModeId = sourceModel.getLanguageId(); - const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.languageService.create(sourceModelModeId), replacePreviewUri); + const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.languageService.createById(sourceModelModeId), replacePreviewUri); this._register(fileMatch.onChange(({ forceUpdateModel }) => this.update(sourceModel, replacePreviewModel, fileMatch, forceUpdateModel))); this._register(this.searchWorkbenchService.searchModel.onReplaceTermChanged(() => this.update(sourceModel, replacePreviewModel, fileMatch))); this._register(fileMatch.onDispose(() => replacePreviewModel.dispose())); // TODO@Sandeep we should not dispose a model directly but rather the reference (depends on https://github.com/microsoft/vscode/issues/17073) diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts index 5017b442fe00a..600700a654a12 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts @@ -67,7 +67,7 @@ class SearchEditorModelFactory { } return Promise.resolve({ - resultsModel: modelService.getModel(resource) ?? modelService.createModel('', languageService.create('search-result'), resource), + resultsModel: modelService.getModel(resource) ?? modelService.createModel('', languageService.createById('search-result'), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -100,7 +100,7 @@ class SearchEditorModelFactory { } return Promise.resolve({ - resultsModel: modelService.createModel(contents ?? '', languageService.create('search-result'), resource), + resultsModel: modelService.createModel(contents ?? '', languageService.createById('search-result'), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -134,7 +134,7 @@ class SearchEditorModelFactory { const { text, config } = await instantiationService.invokeFunction(parseSavedSearchEditor, existingFile); return ({ - resultsModel: modelService.createModel(text ?? '', languageService.create('search-result'), resource), + resultsModel: modelService.createModel(text ?? '', languageService.createById('search-result'), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -151,7 +151,7 @@ class SearchEditorModelFactory { if (!model && backup) { const factory = await createTextBufferFactoryFromStream(backup.value); - model = modelService.createModel(factory, languageService.create('search-result'), resource); + model = modelService.createModel(factory, languageService.createById('search-result'), resource); } if (model) { @@ -159,7 +159,7 @@ class SearchEditorModelFactory { const { text, config } = parseSerializedSearchEditor(existingFile); modelService.destroyModel(resource); return ({ - resultsModel: modelService.createModel(text ?? '', languageService.create('search-result'), resource), + resultsModel: modelService.createModel(text ?? '', languageService.createById('search-result'), resource), configurationModel: new SearchConfigurationModel(config) }); } diff --git a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts index 09b1b5794e35e..956b9d6155059 100644 --- a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts +++ b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts @@ -66,7 +66,7 @@ export class TestingContentProvider implements IWorkbenchContribution, ITextMode text = message; } else if (message) { text = message.value; - language = this.languageService.create('markdown'); + language = this.languageService.createById('markdown'); } break; } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index f861857fe9a1c..550e74f38044f 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -1293,7 +1293,7 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { provideTextContent(uri: URI): Promise | null { if (uri.scheme === USER_DATA_SYNC_SCHEME) { - return this.userDataSyncService.resolveContent(uri).then(content => this.modelService.createModel(content || '', this.languageService.create('jsonc'), uri)); + return this.userDataSyncService.resolveContent(uri).then(content => this.modelService.createModel(content || '', this.languageService.createById('jsonc'), uri)); } return null; } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts index bd781f2fea36d..a7fe7953ef933 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts @@ -72,7 +72,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi renderer.code = (code, lang) => { i++; const languageId = this.languageService.getLanguageIdForLanguageName(lang) || ''; - const languageSelection = this.languageService.create(languageId); + const languageSelection = this.languageService.createById(languageId); // Create all models for this resource in one go... we'll need them all and we don't want to re-parse markdown each time const model = this.modelService.createModel(code, languageSelection, resource.with({ fragment: `${i}.${lang}` })); if (i === j) { codeEditorModel = model; } diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 7db619cdf444a..92297584a79af 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -122,7 +122,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic if (this.isDefaultSettingsResource(uri)) { const target = this.getConfigurationTargetFromDefaultSettingsResource(uri); - const languageSelection = this.languageService.create('jsonc'); + const languageSelection = this.languageService.createById('jsonc'); const model = this._register(this.modelService.createModel('', languageSelection, uri)); let defaultSettings: DefaultSettings | undefined; @@ -150,14 +150,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic if (this.defaultSettingsRawResource.toString() === uri.toString()) { const defaultRawSettingsEditorModel = this.instantiationService.createInstance(DefaultRawSettingsEditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); - const languageSelection = this.languageService.create('jsonc'); + const languageSelection = this.languageService.createById('jsonc'); const model = this._register(this.modelService.createModel(defaultRawSettingsEditorModel.content, languageSelection, uri)); return model; } if (this.defaultKeybindingsResource.toString() === uri.toString()) { const defaultKeybindingsEditorModel = this.instantiationService.createInstance(DefaultKeybindingsEditorModel, uri); - const languageSelection = this.languageService.create('jsonc'); + const languageSelection = this.languageService.createById('jsonc'); const model = this._register(this.modelService.createModel(defaultKeybindingsEditorModel.content, languageSelection, uri)); return model; } diff --git a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts index 2fc90f25c38a1..2130229b2ed61 100644 --- a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts @@ -44,7 +44,7 @@ suite('Workbench - TextModelResolverService', () => { provideTextContent: async function (resource: URI): Promise { if (resource.scheme === 'test') { let modelContent = 'Hello Test'; - let languageSelection = accessor.languageService.create('json'); + let languageSelection = accessor.languageService.createById('json'); return accessor.modelService.createModel(modelContent, languageSelection, resource); } @@ -179,7 +179,7 @@ suite('Workbench - TextModelResolverService', () => { await waitForIt; let modelContent = 'Hello Test'; - let languageSelection = accessor.languageService.create('json'); + let languageSelection = accessor.languageService.createById('json'); return accessor.modelService.createModel(modelContent, languageSelection, resource); } }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts index 063fd4f548261..aa113a0807c97 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts @@ -34,7 +34,7 @@ suite('TextDiffEditorModel', () => { provideTextContent: async function (resource: URI): Promise { if (resource.scheme === 'test') { let modelContent = 'Hello Test'; - let languageSelection = accessor.languageService.create('json'); + let languageSelection = accessor.languageService.createById('json'); return accessor.modelService.createModel(modelContent, languageSelection, resource); } diff --git a/src/vs/workbench/test/browser/parts/editor/textResourceEditorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/textResourceEditorInput.test.ts index 1bc501716b67a..f126d685bf86e 100644 --- a/src/vs/workbench/test/browser/parts/editor/textResourceEditorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/textResourceEditorInput.test.ts @@ -31,7 +31,7 @@ suite('TextResourceEditorInput', () => { test('basics', async () => { const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.createById(PLAINTEXT_MODE_ID), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', undefined, undefined); @@ -47,7 +47,7 @@ suite('TextResourceEditorInput', () => { }); const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.createById(PLAINTEXT_MODE_ID), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', 'resource-input-test', undefined); @@ -68,7 +68,7 @@ suite('TextResourceEditorInput', () => { }); const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.createById(PLAINTEXT_MODE_ID), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', undefined, undefined); input.setPreferredMode('resource-input-test'); @@ -80,7 +80,7 @@ suite('TextResourceEditorInput', () => { test('preferred contents (via ctor)', async () => { const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.createById(PLAINTEXT_MODE_ID), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', undefined, 'My Resource Input Contents'); @@ -97,7 +97,7 @@ suite('TextResourceEditorInput', () => { test('preferred contents (via setPreferredContents)', async () => { const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); - accessor.modelService.createModel('function test() {}', accessor.languageService.create('text'), resource); + accessor.modelService.createModel('function test() {}', accessor.languageService.createById(PLAINTEXT_MODE_ID), resource); const input = instantiationService.createInstance(TextResourceEditorInput, resource, 'The Name', 'The Description', undefined, undefined); input.setPreferredContents('My Resource Input Contents'); From ea12cfecf06f8df3cd04770f4999866dd0a8b9d3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 17:29:53 +0100 Subject: [PATCH 0445/2210] Change to use correct event --- .../notebook/browser/contrib/codeRenderer/codeRenderer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts b/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts index c93a2fb96ca9e..f4d2e7595398f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer.ts @@ -108,8 +108,10 @@ export class NotebookCodeRendererContribution extends Disposable { registerCodeRendererContrib(`text/x-${id}`, id); }); - this._register(_languageService.onDidEncounterLanguage((languageId) => { - registerCodeRendererContrib(`text/x-${languageId}`, languageId); + this._register(_languageService.onLanguagesMaybeChanged(() => { + _languageService.getRegisteredLanguageIds().forEach(id => { + registerCodeRendererContrib(`text/x-${id}`, id); + }); })); registerCodeRendererContrib('application/json', 'json'); From bcc088aa50a9698d6c99dfb8466843ffb2244195 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Dec 2021 17:30:29 +0100 Subject: [PATCH 0446/2210] Remove unnecessary listener (the initial reason the listener was added is now covered by `IModeService.onModelModeChanged`) --- src/vs/workbench/common/resources.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 1e52e04c5a871..919c3efeaa4f5 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -66,7 +66,6 @@ export class ResourceContextKey implements IContextKey { this._isFileSystemResource.set(Boolean(resource && _fileService.hasProvider(resource))); })); - this._disposables.add(_languageService.onDidEncounterLanguage(this._setLangId, this)); this._disposables.add(_modelService.onModelAdded(model => { if (isEqual(model.uri, this.get())) { this._setLangId(); From 212a8ca457177c1b41fd839a1977ba0cd2dabc1a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 8 Dec 2021 10:46:54 -0600 Subject: [PATCH 0447/2210] include detected profiles in getProfiles, use configured icon (#138628) --- src/vs/platform/terminal/node/terminalProfiles.ts | 6 ++++-- .../contrib/terminal/browser/terminalMenus.ts | 2 +- .../terminal/browser/terminalProfileService.ts | 13 +------------ .../test/browser/terminalProfileService.test.ts | 11 ----------- 4 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index d9aab34bc5f7e..63ba4a7259073 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -172,7 +172,7 @@ async function transformToTerminalProfiles( } else { originalPaths = Array.isArray(profile.path) ? profile.path : [profile.path]; args = isWindows ? profile.args : Array.isArray(profile.args) ? profile.args : undefined; - icon = validateIcon(profile.icon) || undefined; + icon = validateIcon(profile.icon); } const paths = (await variableResolver?.(originalPaths)) || originalPaths.slice(); @@ -267,7 +267,8 @@ async function getWslProfiles(wslPath: string, defaultProfileName: string | unde path: wslPath, args: [`-d`, `${distroName}`], isDefault: profileName === defaultProfileName, - icon: getWslIcon(distroName) + icon: getWslIcon(distroName), + isAutoDetected: true }; // Add the profile profiles.push(profile); @@ -327,6 +328,7 @@ function applyConfigProfilesToMap(configProfiles: { [key: string]: IUnresolvedTe if (value === null || (!('path' in value) && !('source' in value))) { profilesMap.delete(profileName); } else { + value.icon = value.icon || profilesMap.get(profileName)?.icon; profilesMap.set(profileName, value); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index f989dec5369b2..e02e162109cd2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -709,7 +709,7 @@ export function getTerminalActionBarArgs(location: ITerminalLocationOptions, pro } { let dropdownActions: IAction[] = []; let submenuActions: IAction[] = []; - + profiles = profiles.filter(e => !e.isAutoDetected); const splitLocation = (location === TerminalLocation.Editor || (typeof location === 'object' && 'viewColumn' in location && location.viewColumn === ACTIVE_GROUP)) ? { viewColumn: SIDE_GROUP } : { splitActiveTerminal: true }; for (const p of profiles) { const isDefault = p.profileName === defaultProfileName; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index db78a9e0a7efe..60c49b4b34b1f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -28,7 +28,6 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA * and keeps the available terminal profiles updated */ export class TerminalProfileService implements ITerminalProfileService { - private _ifNoProfilesTryAgain: boolean = true; private _webExtensionContributedProfileContextKey: IContextKey; private _profilesReadyBarrier: AutoOpenBarrier; private _availableProfiles: ITerminalProfile[] | undefined; @@ -104,17 +103,7 @@ export class TerminalProfileService implements ITerminalProfileService { } protected async _refreshAvailableProfilesNow(): Promise { - const profiles = await this._detectProfiles(); - if (profiles.length === 0 && this._ifNoProfilesTryAgain) { - // available profiles get updated when a terminal is created - // or relevant config changes. - // if there are no profiles, we want to refresh them again - // since terminal creation can't happen in this case and users - // might not think to try changing the config - this._ifNoProfilesTryAgain = false; - await this._refreshAvailableProfilesNow(); - return; - } + const profiles = await this._detectProfiles(true); const profilesChanged = !(equals(profiles, this._availableProfiles, profilesEqual)); const contributedProfilesChanged = await this._updateContributedProfiles(); if (profilesChanged || contributedProfilesChanged) { diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts index e79edc6f2d619..53aab44a21a94 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts @@ -318,17 +318,6 @@ suite('TerminalProfileService', () => { deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); deepStrictEqual(terminalProfileService.contributedProfiles, [jsdebugProfile]); }); - test('should call refreshAvailableProfiles again if no profiles are returned from local/remoteTerminalService', async () => { - terminalInstanceService.setReturnNone(); - const calls: ITerminalProfile[][] = []; - terminalProfileService.onDidChangeAvailableProfiles(e => calls.push(e)); - await terminalProfileService.hasRefreshedProfiles; - deepStrictEqual(calls, [ - [powershellProfile] - ]); - deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); - deepStrictEqual(terminalProfileService.contributedProfiles, [jsdebugProfile]); - }); suite('Profiles Quickpick', () => { let quickInputService: MockQuickInputService; let mockTerminalProfileService: MockTerminalProfileService; From 32fd256a4b4519469fdb14fcdb7c8d6b4a3ca356 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 7 Dec 2021 21:07:25 +0100 Subject: [PATCH 0448/2210] add SymbolKinds.toIcon --- src/vs/editor/common/modes.ts | 107 ++++++------------ .../quickAccess/gotoSymbolQuickAccess.ts | 2 +- .../browser/callHierarchyTree.ts | 3 +- .../browser/outline/documentSymbolsTree.ts | 3 +- .../search/browser/symbolsQuickAccess.ts | 6 +- .../browser/typeHierarchyTree.ts | 3 +- 6 files changed, 43 insertions(+), 81 deletions(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index ceaff9385dd1f..9bdff8a8db019 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -18,7 +18,7 @@ import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureR import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; -import { iconRegistry, Codicon } from 'vs/base/common/codicons'; +import { iconRegistry, Codicon, CSSIcon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; /** * Open ended enum at runtime @@ -1103,84 +1103,43 @@ export const enum SymbolTag { */ export namespace SymbolKinds { - const byName = new Map(); - byName.set('file', SymbolKind.File); - byName.set('module', SymbolKind.Module); - byName.set('namespace', SymbolKind.Namespace); - byName.set('package', SymbolKind.Package); - byName.set('class', SymbolKind.Class); - byName.set('method', SymbolKind.Method); - byName.set('property', SymbolKind.Property); - byName.set('field', SymbolKind.Field); - byName.set('constructor', SymbolKind.Constructor); - byName.set('enum', SymbolKind.Enum); - byName.set('interface', SymbolKind.Interface); - byName.set('function', SymbolKind.Function); - byName.set('variable', SymbolKind.Variable); - byName.set('constant', SymbolKind.Constant); - byName.set('string', SymbolKind.String); - byName.set('number', SymbolKind.Number); - byName.set('boolean', SymbolKind.Boolean); - byName.set('array', SymbolKind.Array); - byName.set('object', SymbolKind.Object); - byName.set('key', SymbolKind.Key); - byName.set('null', SymbolKind.Null); - byName.set('enum-member', SymbolKind.EnumMember); - byName.set('struct', SymbolKind.Struct); - byName.set('event', SymbolKind.Event); - byName.set('operator', SymbolKind.Operator); - byName.set('type-parameter', SymbolKind.TypeParameter); - - const byKind = new Map(); - byKind.set(SymbolKind.File, 'file'); - byKind.set(SymbolKind.Module, 'module'); - byKind.set(SymbolKind.Namespace, 'namespace'); - byKind.set(SymbolKind.Package, 'package'); - byKind.set(SymbolKind.Class, 'class'); - byKind.set(SymbolKind.Method, 'method'); - byKind.set(SymbolKind.Property, 'property'); - byKind.set(SymbolKind.Field, 'field'); - byKind.set(SymbolKind.Constructor, 'constructor'); - byKind.set(SymbolKind.Enum, 'enum'); - byKind.set(SymbolKind.Interface, 'interface'); - byKind.set(SymbolKind.Function, 'function'); - byKind.set(SymbolKind.Variable, 'variable'); - byKind.set(SymbolKind.Constant, 'constant'); - byKind.set(SymbolKind.String, 'string'); - byKind.set(SymbolKind.Number, 'number'); - byKind.set(SymbolKind.Boolean, 'boolean'); - byKind.set(SymbolKind.Array, 'array'); - byKind.set(SymbolKind.Object, 'object'); - byKind.set(SymbolKind.Key, 'key'); - byKind.set(SymbolKind.Null, 'null'); - byKind.set(SymbolKind.EnumMember, 'enum-member'); - byKind.set(SymbolKind.Struct, 'struct'); - byKind.set(SymbolKind.Event, 'event'); - byKind.set(SymbolKind.Operator, 'operator'); - byKind.set(SymbolKind.TypeParameter, 'type-parameter'); + const byKind = new Map(); + byKind.set(SymbolKind.File, Codicon.symbolFile); + byKind.set(SymbolKind.Module, Codicon.symbolModule); + byKind.set(SymbolKind.Namespace, Codicon.symbolNamespace); + byKind.set(SymbolKind.Package, Codicon.symbolPackage); + byKind.set(SymbolKind.Class, Codicon.symbolClass); + byKind.set(SymbolKind.Method, Codicon.symbolMethod); + byKind.set(SymbolKind.Property, Codicon.symbolProperty); + byKind.set(SymbolKind.Field, Codicon.symbolField); + byKind.set(SymbolKind.Constructor, Codicon.symbolConstructor); + byKind.set(SymbolKind.Enum, Codicon.symbolEnum); + byKind.set(SymbolKind.Interface, Codicon.symbolInterface); + byKind.set(SymbolKind.Function, Codicon.symbolFunction); + byKind.set(SymbolKind.Variable, Codicon.symbolVariable); + byKind.set(SymbolKind.Constant, Codicon.symbolConstant); + byKind.set(SymbolKind.String, Codicon.symbolString); + byKind.set(SymbolKind.Number, Codicon.symbolNumber); + byKind.set(SymbolKind.Boolean, Codicon.symbolBoolean); + byKind.set(SymbolKind.Array, Codicon.symbolArray); + byKind.set(SymbolKind.Object, Codicon.symbolObject); + byKind.set(SymbolKind.Key, Codicon.symbolKey); + byKind.set(SymbolKind.Null, Codicon.symbolNull); + byKind.set(SymbolKind.EnumMember, Codicon.symbolEnumMember); + byKind.set(SymbolKind.Struct, Codicon.symbolStruct); + byKind.set(SymbolKind.Event, Codicon.symbolEvent); + byKind.set(SymbolKind.Operator, Codicon.symbolOperator); + byKind.set(SymbolKind.TypeParameter, Codicon.symbolTypeParameter); /** * @internal */ - export function fromString(value: string): SymbolKind | undefined { - return byName.get(value); - } - /** - * @internal - */ - export function toString(kind: SymbolKind): string | undefined { - return byKind.get(kind); - } - /** - * @internal - */ - export function toCssClassName(kind: SymbolKind, inline?: boolean): string { - const symbolName = byKind.get(kind); - let codicon = symbolName && iconRegistry.get('symbol-' + symbolName); - if (!codicon) { + export function toIcon(kind: SymbolKind): CSSIcon { + let icon = byKind.get(kind); + if (!icon) { console.info('No codicon found for SymbolKind ' + kind); - codicon = Codicon.symbolProperty; + icon = Codicon.symbolProperty; } - return `${inline ? 'inline' : 'block'} ${codicon.classNames}`; + return icon; } } diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index ddc4b3e90aff3..8a79f95f1d0ee 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -235,7 +235,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit const symbol = symbols[index]; const symbolLabel = trim(symbol.name); - const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + const symbolLabelWithIcon = `$(${SymbolKinds.toIcon(symbol.kind).id}) ${symbolLabel}`; const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length; let containerLabel = symbol.containerName; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index c1bc2e5377d48..1be8e5b2d0c6a 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -14,6 +14,7 @@ import { compare } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { localize } from 'vs/nls'; +import { CSSIcon } from 'vs/base/common/codicons'; export class Call { constructor( @@ -118,7 +119,7 @@ export class CallRenderer implements ITreeRenderer, _index: number, template: CallRenderingTemplate): void { const { element, filterData } = node; const deprecated = element.item.tags?.includes(SymbolTag.Deprecated); - template.icon.className = SymbolKinds.toCssClassName(element.item.kind, true); + template.icon.classList.add('inline', ...CSSIcon.asClassNameArray(SymbolKinds.toIcon(element.item.kind))); template.label.setLabel( element.item.name, element.item.detail, diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 2f34a0ee93c70..8c606fe87bc42 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -23,6 +23,7 @@ import { IdleValue } from 'vs/base/common/async'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IOutlineComparator, OutlineConfigKeys } from 'vs/workbench/services/outline/browser/outline'; +import { CSSIcon } from 'vs/base/common/codicons'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -141,7 +142,7 @@ export class DocumentSymbolRenderer implements ITreeRenderer= 0) { options.extraClasses!.push(`deprecated`); diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index 159ed9e7e721b..cf1f3e760ec82 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -129,7 +129,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider, _index: number, template: TypeRenderingTemplate): void { const { element, filterData } = node; const deprecated = element.item.tags?.includes(SymbolTag.Deprecated); - template.icon.className = SymbolKinds.toCssClassName(element.item.kind, true); + template.icon.classList.add('inline', ...CSSIcon.asClassNameArray(SymbolKinds.toIcon(element.item.kind))); template.label.setLabel( element.item.name, element.item.detail, From c756741273e56577085c920b09b0b6d8b340305d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 7 Dec 2021 21:57:24 +0100 Subject: [PATCH 0449/2210] CompletionItemKinds.toIcon & fromString --- src/vs/base/common/codicons.ts | 1 + src/vs/editor/common/modes.ts | 170 +++++++++--------- .../editor/contrib/suggest/suggestMemory.ts | 6 +- .../contrib/suggest/suggestWidgetRenderer.ts | 6 +- .../workbench/contrib/debug/browser/repl.ts | 4 +- 5 files changed, 98 insertions(+), 89 deletions(-) diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index c1084bbfd7646..40423a5231016 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -402,6 +402,7 @@ export class Codicon implements CSSIcon { public static readonly starHalf = new Codicon('star-half', { fontCharacter: '\\eb5a' }); public static readonly symbolClass = new Codicon('symbol-class', { fontCharacter: '\\eb5b' }); public static readonly symbolColor = new Codicon('symbol-color', { fontCharacter: '\\eb5c' }); + public static readonly symbolCustomColor = new Codicon('symbol-customcolor', { fontCharacter: '\\eb5c' }); public static readonly symbolConstant = new Codicon('symbol-constant', { fontCharacter: '\\eb5d' }); public static readonly symbolEnumMember = new Codicon('symbol-enum-member', { fontCharacter: '\\eb5e' }); public static readonly symbolField = new Codicon('symbol-field', { fontCharacter: '\\eb5f' }); diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 9bdff8a8db019..d171153ef1844 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -18,7 +18,7 @@ import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureR import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; -import { iconRegistry, Codicon, CSSIcon } from 'vs/base/common/codicons'; +import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; /** * Open ended enum at runtime @@ -371,94 +371,102 @@ export const enum CompletionItemKind { /** * @internal */ -export const completionKindToCssClass = (function () { - let data = Object.create(null); - data[CompletionItemKind.Method] = 'symbol-method'; - data[CompletionItemKind.Function] = 'symbol-function'; - data[CompletionItemKind.Constructor] = 'symbol-constructor'; - data[CompletionItemKind.Field] = 'symbol-field'; - data[CompletionItemKind.Variable] = 'symbol-variable'; - data[CompletionItemKind.Class] = 'symbol-class'; - data[CompletionItemKind.Struct] = 'symbol-struct'; - data[CompletionItemKind.Interface] = 'symbol-interface'; - data[CompletionItemKind.Module] = 'symbol-module'; - data[CompletionItemKind.Property] = 'symbol-property'; - data[CompletionItemKind.Event] = 'symbol-event'; - data[CompletionItemKind.Operator] = 'symbol-operator'; - data[CompletionItemKind.Unit] = 'symbol-unit'; - data[CompletionItemKind.Value] = 'symbol-value'; - data[CompletionItemKind.Constant] = 'symbol-constant'; - data[CompletionItemKind.Enum] = 'symbol-enum'; - data[CompletionItemKind.EnumMember] = 'symbol-enum-member'; - data[CompletionItemKind.Keyword] = 'symbol-keyword'; - data[CompletionItemKind.Snippet] = 'symbol-snippet'; - data[CompletionItemKind.Text] = 'symbol-text'; - data[CompletionItemKind.Color] = 'symbol-color'; - data[CompletionItemKind.File] = 'symbol-file'; - data[CompletionItemKind.Reference] = 'symbol-reference'; - data[CompletionItemKind.Customcolor] = 'symbol-customcolor'; - data[CompletionItemKind.Folder] = 'symbol-folder'; - data[CompletionItemKind.TypeParameter] = 'symbol-type-parameter'; - data[CompletionItemKind.User] = 'account'; - data[CompletionItemKind.Issue] = 'issues'; - - return function (kind: CompletionItemKind): string { - const name = data[kind]; - let codicon = name && iconRegistry.get(name); +export namespace CompletionItemKinds { + + const byKind = new Map(); + byKind.set(CompletionItemKind.Method, Codicon.symbolMethod); + byKind.set(CompletionItemKind.Function, Codicon.symbolFunction); + byKind.set(CompletionItemKind.Constructor, Codicon.symbolConstructor); + byKind.set(CompletionItemKind.Field, Codicon.symbolField); + byKind.set(CompletionItemKind.Variable, Codicon.symbolVariable); + byKind.set(CompletionItemKind.Class, Codicon.symbolClass); + byKind.set(CompletionItemKind.Struct, Codicon.symbolStruct); + byKind.set(CompletionItemKind.Interface, Codicon.symbolInterface); + byKind.set(CompletionItemKind.Module, Codicon.symbolModule); + byKind.set(CompletionItemKind.Property, Codicon.symbolProperty); + byKind.set(CompletionItemKind.Event, Codicon.symbolEvent); + byKind.set(CompletionItemKind.Operator, Codicon.symbolOperator); + byKind.set(CompletionItemKind.Unit, Codicon.symbolUnit); + byKind.set(CompletionItemKind.Value, Codicon.symbolValue); + byKind.set(CompletionItemKind.Enum, Codicon.symbolEnum); + byKind.set(CompletionItemKind.Constant, Codicon.symbolConstant); + byKind.set(CompletionItemKind.Enum, Codicon.symbolEnum); + byKind.set(CompletionItemKind.EnumMember, Codicon.symbolEnumMember); + byKind.set(CompletionItemKind.Keyword, Codicon.symbolKeyword); + byKind.set(CompletionItemKind.Snippet, Codicon.symbolSnippet); + byKind.set(CompletionItemKind.Text, Codicon.symbolText); + byKind.set(CompletionItemKind.Color, Codicon.symbolColor); + byKind.set(CompletionItemKind.File, Codicon.symbolFile); + byKind.set(CompletionItemKind.Reference, Codicon.symbolReference); + byKind.set(CompletionItemKind.Customcolor, Codicon.symbolCustomColor); + byKind.set(CompletionItemKind.Folder, Codicon.symbolFolder); + byKind.set(CompletionItemKind.TypeParameter, Codicon.symbolTypeParameter); + byKind.set(CompletionItemKind.User, Codicon.account); + byKind.set(CompletionItemKind.Issue, Codicon.issues); + + /** + * @internal + */ + export function toIcon(kind: CompletionItemKind): CSSIcon { + let codicon = byKind.get(kind); if (!codicon) { console.info('No codicon found for CompletionItemKind ' + kind); codicon = Codicon.symbolProperty; } - return codicon.classNames; - }; -})(); - -/** - * @internal - */ -export let completionKindFromString: { - (value: string): CompletionItemKind; - (value: string, strict: true): CompletionItemKind | undefined; -} = (function () { - let data: Record = Object.create(null); - data['method'] = CompletionItemKind.Method; - data['function'] = CompletionItemKind.Function; - data['constructor'] = CompletionItemKind.Constructor; - data['field'] = CompletionItemKind.Field; - data['variable'] = CompletionItemKind.Variable; - data['class'] = CompletionItemKind.Class; - data['struct'] = CompletionItemKind.Struct; - data['interface'] = CompletionItemKind.Interface; - data['module'] = CompletionItemKind.Module; - data['property'] = CompletionItemKind.Property; - data['event'] = CompletionItemKind.Event; - data['operator'] = CompletionItemKind.Operator; - data['unit'] = CompletionItemKind.Unit; - data['value'] = CompletionItemKind.Value; - data['constant'] = CompletionItemKind.Constant; - data['enum'] = CompletionItemKind.Enum; - data['enum-member'] = CompletionItemKind.EnumMember; - data['enumMember'] = CompletionItemKind.EnumMember; - data['keyword'] = CompletionItemKind.Keyword; - data['snippet'] = CompletionItemKind.Snippet; - data['text'] = CompletionItemKind.Text; - data['color'] = CompletionItemKind.Color; - data['file'] = CompletionItemKind.File; - data['reference'] = CompletionItemKind.Reference; - data['customcolor'] = CompletionItemKind.Customcolor; - data['folder'] = CompletionItemKind.Folder; - data['type-parameter'] = CompletionItemKind.TypeParameter; - data['typeParameter'] = CompletionItemKind.TypeParameter; - data['account'] = CompletionItemKind.User; - data['issue'] = CompletionItemKind.Issue; - return function (value: string, strict?: true) { - let res = data[value]; + return codicon; + } + + const data = new Map(); + data.set('method', CompletionItemKind.Method); + data.set('function', CompletionItemKind.Function); + data.set('constructor', CompletionItemKind.Constructor); + data.set('field', CompletionItemKind.Field); + data.set('variable', CompletionItemKind.Variable); + data.set('class', CompletionItemKind.Class); + data.set('struct', CompletionItemKind.Struct); + data.set('interface', CompletionItemKind.Interface); + data.set('module', CompletionItemKind.Module); + data.set('property', CompletionItemKind.Property); + data.set('event', CompletionItemKind.Event); + data.set('operator', CompletionItemKind.Operator); + data.set('unit', CompletionItemKind.Unit); + data.set('value', CompletionItemKind.Value); + data.set('constant', CompletionItemKind.Constant); + data.set('enum', CompletionItemKind.Enum); + data.set('enum-member', CompletionItemKind.EnumMember); + data.set('enumMember', CompletionItemKind.EnumMember); + data.set('keyword', CompletionItemKind.Keyword); + data.set('snippet', CompletionItemKind.Snippet); + data.set('text', CompletionItemKind.Text); + data.set('color', CompletionItemKind.Color); + data.set('file', CompletionItemKind.File); + data.set('reference', CompletionItemKind.Reference); + data.set('customcolor', CompletionItemKind.Customcolor); + data.set('folder', CompletionItemKind.Folder); + data.set('type-parameter', CompletionItemKind.TypeParameter); + data.set('typeParameter', CompletionItemKind.TypeParameter); + data.set('account', CompletionItemKind.User); + data.set('issue', CompletionItemKind.Issue); + + /** + * @internal + */ + export function fromString(value: string): CompletionItemKind; + /** + * @internal + */ + export function fromString(value: string, strict: true): CompletionItemKind | undefined; + /** + * @internal + */ + export function fromString(value: string, strict?: boolean): CompletionItemKind | undefined { + let res = data.get(value); if (typeof res === 'undefined' && !strict) { res = CompletionItemKind.Property; } return res; - }; -})(); + } +} export interface CompletionItemLabel { label: string; diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index 96f134cc78dfb..83fbeaf7d66b5 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -9,7 +9,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { LRUCache, TernarySearchTree } from 'vs/base/common/map'; import { IPosition } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; -import { CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; +import { CompletionItemKind, CompletionItemKinds } from 'vs/editor/common/modes'; import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -138,7 +138,7 @@ export class LRUMemory extends Memory { let seq = 0; for (const [key, value] of data) { value.touch = seq; - value.type = typeof value.type === 'number' ? value.type : completionKindFromString(value.type); + value.type = typeof value.type === 'number' ? value.type : CompletionItemKinds.fromString(value.type); this._cache.set(key, value); } this._seq = this._cache.size; @@ -206,7 +206,7 @@ export class PrefixMemory extends Memory { if (data.length > 0) { this._seq = data[0][1].touch + 1; for (const [key, value] of data) { - value.type = typeof value.type === 'number' ? value.type : completionKindFromString(value.type); + value.type = typeof value.type === 'number' ? value.type : CompletionItemKinds.fromString(value.type); this._trie.set(key, value); } } diff --git a/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts b/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts index 68655b1697e87..6685357e281d7 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetRenderer.ts @@ -8,14 +8,14 @@ import { $, append, hide, show } from 'vs/base/browser/dom'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IListRenderer } from 'vs/base/browser/ui/list/list'; import { flatten } from 'vs/base/common/arrays'; -import { Codicon } from 'vs/base/common/codicons'; +import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { createMatches } from 'vs/base/common/filters'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { CompletionItemKind, CompletionItemTag, completionKindToCssClass } from 'vs/editor/common/modes'; +import { CompletionItemKind, CompletionItemKinds, CompletionItemTag } from 'vs/editor/common/modes'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILanguageService } from 'vs/editor/common/services/languageService'; @@ -198,7 +198,7 @@ export class ItemRenderer implements IListRenderer= 0) { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index a6c347ead2839..ef13f7d520279 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -30,7 +30,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IDecorationOptions } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; -import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, completionKindFromString, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes'; +import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemKinds, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; @@ -252,7 +252,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { suggestions.push({ label: item.label, insertText, - kind: completionKindFromString(item.type || 'property'), + kind: CompletionItemKinds.fromString(item.type || 'property'), filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, range: computeRange(item.length || overwriteBefore), sortText: item.sortText, From cc731ad6b55823c420585b066d39674049a22c21 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Dec 2021 16:37:50 +0100 Subject: [PATCH 0450/2210] add IconContribution.getDefinition --- src/vs/platform/theme/browser/iconsStyleSheet.ts | 11 +++-------- src/vs/platform/theme/common/iconRegistry.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/theme/browser/iconsStyleSheet.ts b/src/vs/platform/theme/browser/iconsStyleSheet.ts index a255ac5c10ba8..3f51ab99aea1c 100644 --- a/src/vs/platform/theme/browser/iconsStyleSheet.ts +++ b/src/vs/platform/theme/browser/iconsStyleSheet.ts @@ -6,7 +6,6 @@ import { asCSSPropertyValue, asCSSUrl } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { getIconRegistry, IconContribution, IconFontContribution } from 'vs/platform/theme/common/iconRegistry'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export interface IIconsStyleSheet { @@ -24,13 +23,9 @@ export function getIconsStyleSheet(): IIconsStyleSheet { getCSS() { const usedFontIds: { [id: string]: IconFontContribution } = {}; const formatIconRule = (contribution: IconContribution): string | undefined => { - let definition = contribution.defaults; - while (ThemeIcon.isThemeIcon(definition)) { - const c = iconRegistry.getIcon(definition.id); - if (!c) { - return undefined; - } - definition = c.defaults; + const definition = IconContribution.getDefinition(contribution, iconRegistry); + if (!definition) { + return undefined; } const fontId = definition.fontId; if (fontId) { diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index 928e51519182a..ed39819164d38 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -27,6 +27,20 @@ export interface IconDefinition { fontCharacter: string; } +export namespace IconContribution { + export function getDefinition(contribution: IconContribution, registry: IIconRegistry): IconDefinition | undefined { + let definition = contribution.defaults; + while (ThemeIcon.isThemeIcon(definition)) { + const c = iconRegistry.getIcon(definition.id); + if (!c) { + return undefined; + } + definition = c.defaults; + } + return definition; + } +} + export interface IconContribution { id: string; description: string | undefined; From 1b2e3e2f338d9d284e4a29138d4b97bc30de383e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Dec 2021 17:37:13 +0100 Subject: [PATCH 0451/2210] Move away from codicon.iconRegistry. Use theme/common/iconRegistry --- src/vs/platform/theme/common/themeService.ts | 4 ++++ .../workbench/browser/parts/titlebar/titlebarPart.ts | 11 ++++------- .../banner/browser/welcomeBanner.contribution.ts | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index ef3c013d23c1b..28ed9b71931d3 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -51,6 +51,10 @@ export namespace ThemeIcon { return { id: name }; } + export function fromId(id: string) : ThemeIcon { + return { id }; + } + export function modify(icon: ThemeIcon, modifier: 'disabled' | 'spin' | undefined): ThemeIcon { let id = icon.id; const tildeIndex = id.lastIndexOf('~'); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 9289a640ca37f..9eb463b6beec9 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -41,13 +41,13 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { Codicon, iconRegistry } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { getVirtualWorkspaceLocation } from 'vs/platform/remote/common/remoteHosts'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { getIconRegistry, registerIcon } from 'vs/platform/theme/common/iconRegistry'; const layoutControlIcon = registerIcon('layout-control', Codicon.layout, localize('layoutControlIcon', "Icon for the layout control menu found in the title bar.")); @@ -380,13 +380,10 @@ export class TitlebarPart extends Part implements ITitleService { if (isWeb) { const homeIndicator = this.environmentService.options?.homeIndicator; if (homeIndicator) { - let codicon = iconRegistry.get(homeIndicator.icon); - if (!codicon) { - codicon = Codicon.code; - } + const icon: ThemeIcon = getIconRegistry().getIcon(homeIndicator.icon) ? { id: homeIndicator.icon } : Codicon.code; this.appIcon.setAttribute('href', homeIndicator.href); - this.appIcon.classList.add(...codicon.classNamesArray); + this.appIcon.classList.add(...ThemeIcon.asClassNameArray(icon)); this.appIconBadge = document.createElement('div'); this.appIconBadge.classList.add('home-bar-icon-badge'); this.appIcon.appendChild(this.appIconBadge); diff --git a/src/vs/workbench/contrib/welcome/banner/browser/welcomeBanner.contribution.ts b/src/vs/workbench/contrib/welcome/banner/browser/welcomeBanner.contribution.ts index 0eb1dd682d285..a29b3b1a8f01c 100644 --- a/src/vs/workbench/contrib/welcome/banner/browser/welcomeBanner.contribution.ts +++ b/src/vs/workbench/contrib/welcome/banner/browser/welcomeBanner.contribution.ts @@ -7,10 +7,10 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; -import { Codicon, iconRegistry } from 'vs/base/common/codicons'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { URI } from 'vs/base/common/uri'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; class WelcomeBannerContribution { @@ -30,9 +30,9 @@ class WelcomeBannerContribution { return; // welcome banner dismissed } - let icon: Codicon | URI | undefined = undefined; + let icon: ThemeIcon | URI | undefined = undefined; if (typeof welcomeBanner.icon === 'string') { - icon = iconRegistry.get(welcomeBanner.icon); + icon = ThemeIcon.fromId(welcomeBanner.icon); } else if (welcomeBanner.icon) { icon = URI.revive(welcomeBanner.icon); } From b882ae5646f2ef0294f99442235223305f42638d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Dec 2021 17:37:28 +0100 Subject: [PATCH 0452/2210] DecorationService: use theme/common/iconRegistry for ThemeIcons --- .../decorations/browser/decorationsService.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index b916895e81c53..165bdb6542930 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -10,7 +10,7 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; -import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; +import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector, asCSSPropertyValue } from 'vs/base/browser/dom'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; @@ -19,9 +19,9 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { hash } from 'vs/base/common/hash'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { iconRegistry } from 'vs/base/common/codicons'; import { asArray, distinct } from 'vs/base/common/arrays'; import { asCssVariableName } from 'vs/platform/theme/common/colorRegistry'; +import { getIconRegistry, IconContribution } from 'vs/platform/theme/common/iconRegistry'; class DecorationRule { @@ -121,20 +121,23 @@ class DecorationRule { private _createIconCSSRule(icon: ThemeIcon, color: string | undefined, element: HTMLStyleElement) { - const index = icon.id.lastIndexOf('~'); - const id = index < 0 ? icon.id : icon.id.substr(0, index); - const modifier = index < 0 ? '' : icon.id.substr(index + 1); - - const codicon = iconRegistry.get(id); - if (!codicon || !('fontCharacter' in codicon.definition)) { + const modifier = ThemeIcon.getModifier(icon); + if (modifier) { + icon = ThemeIcon.modify(icon, undefined); + } + const iconContribution = getIconRegistry().getIcon(icon.id); + if (!iconContribution) { + return; + } + const definition = IconContribution.getDefinition(iconContribution, getIconRegistry()); + if (!definition) { return; } - const charCode = parseInt(codicon.definition.fontCharacter.substr(1), 16); createCSSRule( `.${this.iconBadgeClassName}::after`, - `content: "${String.fromCharCode(charCode)}"; + `content: '${definition.fontCharacter}'; color: ${getColor(color)}; - font-family: codicon; + font-family: ${asCSSPropertyValue(definition.fontId ?? 'codicon')}; font-size: 16px; margin-right: 14px; font-weight: normal; From ea242ec8efc9e98c898d4ec375cd40d29eccbe9d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Dec 2021 18:08:37 +0100 Subject: [PATCH 0453/2210] Terminal: Don't use codicon.iconRegistry, use platform/theme/common/iconRegistry --- .../contrib/terminal/browser/terminalIcon.ts | 5 +++-- .../terminal/browser/terminalInstance.ts | 9 ++++---- .../browser/terminalProfileQuickpick.ts | 16 ++++++++++---- .../browser/terminalProfileResolverService.ts | 4 ++-- .../terminal/browser/terminalService.ts | 21 +++++++++---------- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts index ff25f7882b45d..b098cc3ece1d5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon, iconRegistry } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { hash } from 'vs/base/common/hash'; import { URI } from 'vs/base/common/uri'; import { IExtensionTerminalProfile, ITerminalProfile } from 'vs/platform/terminal/common/terminal'; +import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -94,7 +95,7 @@ export function getUriClasses(terminal: ITerminalInstance | IExtensionTerminalPr let uri = undefined; if (extensionContributed) { - if (typeof icon === 'string' && (icon.startsWith('$(') || iconRegistry.get(icon))) { + if (typeof icon === 'string' && (icon.startsWith('$(') || getIconRegistry().getIcon(icon))) { return iconClasses; } else if (typeof icon === 'string') { uri = URI.parse(icon); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 407f1fe26a0fc..94547d7ee3b40 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1798,15 +1798,16 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } async changeIcon() { - const items: IQuickPickItem[] = []; + type Item = IQuickPickItem & { icon: TerminalIcon }; + const items: Item[] = []; for (const icon of iconRegistry.all) { - items.push({ label: `$(${icon.id})`, description: `${icon.id}` }); + items.push({ label: `$(${icon.id})`, description: `${icon.id}`, icon }); } const result = await this._quickInputService.pick(items, { matchOnDescription: true }); - if (result && result.description) { - this._icon = iconRegistry.get(result.description); + if (result) { + this._icon = result.icon; this._onIconChanged.fire(this); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index 1f2efc11842f9..04abfd38d35f4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { iconRegistry, Codicon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IQuickInputService, IKeyMods, IPickOptions, IQuickPickSeparator, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IExtensionTerminalProfile, ITerminalProfile, ITerminalProfileObject, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal'; @@ -14,6 +14,7 @@ import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService' import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; type DefaultProfileName = string; @@ -144,10 +145,17 @@ export class TerminalProfileQuickpick { quickPickItems.push({ type: 'separator', label: nls.localize('ICreateContributedTerminalProfileOptions', "contributed") }); const contributedProfiles: IProfileQuickPickItem[] = []; for (const contributed of this._terminalProfileService.contributedProfiles) { - if (typeof contributed.icon === 'string' && contributed.icon.startsWith('$(')) { - contributed.icon = contributed.icon.substring(2, contributed.icon.length - 1); + let icon: ThemeIcon | undefined; + if (typeof contributed.icon === 'string') { + if (contributed.icon.startsWith('$(')) { + icon = ThemeIcon.fromString(contributed.icon); + } else { + icon = ThemeIcon.fromId(contributed.icon); + } + } + if (!icon || !getIconRegistry().getIcon(icon.id)) { + icon = Codicon.terminal; } - const icon = contributed.icon && typeof contributed.icon === 'string' ? (iconRegistry.get(contributed.icon) || Codicon.terminal) : Codicon.terminal; const uriClasses = getUriClasses(contributed, this._themeService.getColorTheme().type, true); const colorClass = getColorClass(contributed); const iconClasses = []; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index fbb98f86b9d6a..c7ce30438a423 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -15,7 +15,7 @@ import { IProcessEnvironment, OperatingSystem, OS } from 'vs/base/common/platfor import { IShellLaunchConfig, ITerminalProfile, ITerminalProfileObject, TerminalIcon, TerminalSettingId, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal'; import { IShellLaunchConfigResolveOptions, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import * as path from 'vs/base/common/path'; -import { Codicon, iconRegistry } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { debounce } from 'vs/base/common/decorators'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -172,7 +172,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro return undefined; } if (typeof icon === 'string') { - return iconRegistry.get(icon); + return ThemeIcon.fromId(icon); } if (ThemeIcon.isThemeIcon(icon)) { return icon; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 9610f47fd53f0..552c8fa1efa51 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -5,7 +5,6 @@ import * as dom from 'vs/base/browser/dom'; import { timeout } from 'vs/base/common/async'; -import { Codicon, iconRegistry } from 'vs/base/common/codicons'; import { debounce } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -20,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString } from 'vs/platform/terminal/common/terminal'; import { iconForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IconDefinition } from 'vs/platform/theme/common/iconRegistry'; +import { getIconRegistry, IconContribution } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { VirtualWorkspaceContext } from 'vs/workbench/browser/contextkeys'; @@ -1126,16 +1125,16 @@ class TerminalEditorStyle extends Themable { ); } if (ThemeIcon.isThemeIcon(icon)) { - const codicon = iconRegistry.get(icon.id); - if (codicon) { - let def: Codicon | IconDefinition = codicon; - while ('definition' in def) { - def = def.definition; + const iconRegistry = getIconRegistry(); + const iconContribution = iconRegistry.getIcon(icon.id); + if (iconContribution) { + const def = IconContribution.getDefinition(iconContribution, iconRegistry); + if (def) { + css += ( + `.monaco-workbench .terminal-tab.codicon-${icon.id}::before` + + `{content: '${def.fontCharacter}' !important; font-family: ${dom.asCSSPropertyValue(def.fontId ?? 'codicon')} !important;}` + ); } - css += ( - `.monaco-workbench .terminal-tab.codicon-${icon.id}::before` + - `{content: '${def.fontCharacter}' !important;}` - ); } } } From df4110a998151c94bcc72978efc5c4c4df156d0f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Dec 2021 18:18:16 +0100 Subject: [PATCH 0454/2210] Replace codicon.iconRegistry with Codicon.getAll() --- src/vs/base/common/codicons.ts | 48 +++++-------------- .../common/terminalPlatformConfiguration.ts | 6 +-- src/vs/platform/theme/common/iconRegistry.ts | 17 ++++--- .../terminal/browser/terminalInstance.ts | 4 +- 4 files changed, 25 insertions(+), 50 deletions(-) diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 40423a5231016..b4b1b2d959c02 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; export interface IIconRegistry { readonly all: IterableIterator; @@ -11,40 +11,6 @@ export interface IIconRegistry { get(id: string): Codicon | undefined; } -class Registry implements IIconRegistry { - - private readonly _icons = new Map(); - private readonly _onDidRegister = new Emitter(); - - public add(icon: Codicon) { - const existing = this._icons.get(icon.id); - if (!existing) { - this._icons.set(icon.id, icon); - this._onDidRegister.fire(icon); - } else if (icon.description) { - existing.description = icon.description; - } else { - console.error(`Duplicate registration of codicon ${icon.id}`); - } - } - - public get(id: string): Codicon | undefined { - return this._icons.get(id); - } - - public get all(): IterableIterator { - return this._icons.values(); - } - - public get onDidRegister(): Event { - return this._onDidRegister.event; - } -} - -const _registry = new Registry(); - -export const iconRegistry: IIconRegistry = _registry; - // Selects all codicon names encapsulated in the `$()` syntax and wraps the // results with spaces so that screen readers can read the text better. export function getCodiconAriaLabel(text: string | undefined) { @@ -63,14 +29,24 @@ export function getCodiconAriaLabel(text: string | undefined) { * In that call a Codicon can be names as default. */ export class Codicon implements CSSIcon { + private constructor(public readonly id: string, public readonly definition: IconDefinition, public description?: string) { - _registry.add(this); + Codicon._allCodicons.push(this); } public get classNames() { return 'codicon codicon-' + this.id; } // classNamesArray is useful for migrating to ES6 classlist public get classNamesArray() { return ['codicon', 'codicon-' + this.id]; } public get cssSelector() { return '.codicon.codicon-' + this.id; } + // registry + private static _allCodicons : Codicon[] = []; + + /** + * @returns Returns all Codicons. Only to be used by the icon registry in platform. + */ + public static getAll() : readonly Codicon[] { + return Codicon._allCodicons; + } // built-in icons, with image name public static readonly add = new Codicon('add', { fontCharacter: '\\ea60' }); diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts index ca3e4d5730070..207b19f41dfcb 100644 --- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts +++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { iconRegistry } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { OperatingSystem } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; @@ -27,8 +27,8 @@ const terminalProfileBaseProperties: IJSONSchemaMap = { icon: { description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'), type: 'string', - enum: Array.from(iconRegistry.all, icon => icon.id), - markdownEnumDescriptions: Array.from(iconRegistry.all, icon => `$(${icon.id})`), + enum: Array.from(Codicon.getAll(), icon => icon.id), + markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`), }, color: { description: localize('terminalProfile.color', 'A theme color ID to associate with this terminal.'), diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index ed39819164d38..163fd5b143e54 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { RunOnceScheduler } from 'vs/base/common/async'; -import * as Codicons from 'vs/base/common/codicons'; +import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { URI } from 'vs/base/common/uri'; @@ -138,7 +138,7 @@ class IconRegistry implements IIconRegistry { type: 'object', properties: {} }; - private iconReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', pattern: `^${Codicons.CSSIcon.iconNameExpression}$`, enum: [], enumDescriptions: [] }; + private iconReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', pattern: `^${CSSIcon.iconNameExpression}$`, enum: [], enumDescriptions: [] }; private iconFontsById: { [key: string]: IconFontContribution }; @@ -275,10 +275,9 @@ export function getIconRegistry(): IIconRegistry { } function initialize() { - for (const icon of Codicons.iconRegistry.all) { + for (const icon of Codicon.getAll()) { iconRegistry.registerIcon(icon.id, icon.definition, icon.description); } - Codicons.iconRegistry.onDidRegister(icon => iconRegistry.registerIcon(icon.id, icon.definition, icon.description)); } initialize(); @@ -300,10 +299,10 @@ iconRegistry.onDidChange(() => { // common icons -export const widgetClose = registerIcon('widget-close', Codicons.Codicon.close, localize('widgetClose', 'Icon for the close action in widgets.')); +export const widgetClose = registerIcon('widget-close', Codicon.close, localize('widgetClose', 'Icon for the close action in widgets.')); -export const gotoPreviousLocation = registerIcon('goto-previous-location', Codicons.Codicon.arrowUp, localize('previousChangeIcon', 'Icon for goto previous editor location.')); -export const gotoNextLocation = registerIcon('goto-next-location', Codicons.Codicon.arrowDown, localize('nextChangeIcon', 'Icon for goto next editor location.')); +export const gotoPreviousLocation = registerIcon('goto-previous-location', Codicon.arrowUp, localize('previousChangeIcon', 'Icon for goto previous editor location.')); +export const gotoNextLocation = registerIcon('goto-next-location', Codicon.arrowDown, localize('nextChangeIcon', 'Icon for goto next editor location.')); -export const syncing = ThemeIcon.modify(Codicons.Codicon.sync, 'spin'); -export const spinningLoading = ThemeIcon.modify(Codicons.Codicon.loading, 'spin'); +export const syncing = ThemeIcon.modify(Codicon.sync, 'spin'); +export const spinningLoading = ThemeIcon.modify(Codicon.loading, 'spin'); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 94547d7ee3b40..77c9fcb56f5cc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -42,7 +42,7 @@ import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITe import { IProductService } from 'vs/platform/product/common/productService'; import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { AutoOpenBarrier, Promises } from 'vs/base/common/async'; -import { Codicon, iconRegistry } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { ITerminalStatusList, TerminalStatus, TerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -1800,7 +1800,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { async changeIcon() { type Item = IQuickPickItem & { icon: TerminalIcon }; const items: Item[] = []; - for (const icon of iconRegistry.all) { + for (const icon of Codicon.getAll()) { items.push({ label: `$(${icon.id})`, description: `${icon.id}`, icon }); } const result = await this._quickInputService.pick(items, { From 6701d05a701d3ca5432cb04cf6dd129b656696b0 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Wed, 8 Dec 2021 09:31:37 -0800 Subject: [PATCH 0455/2210] Add title, fixes #107748 --- src/vs/workbench/contrib/preferences/browser/settingsTree.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index bd3d34e168c4d..eac0cf350e16c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -773,6 +773,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const syncIgnoredElement = DOM.append(container, $('span.setting-item-ignored')); const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement); syncIgnoredLabel.text = `($(sync-ignored) ${localize('extensionSyncIgnoredLabel', 'Sync: Ignored')})`; + syncIgnoredLabel.title = localize('syncIgnoredTitle', "Settings sync does not sync this setting"); return syncIgnoredElement; } From 3baa168461b809b98612c1fef46e6a5dd50b2bf2 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 8 Dec 2021 14:41:28 -0500 Subject: [PATCH 0456/2210] Up distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2336171c6c2ef..40b41b3d4025a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.63.0", - "distro": "28dea56425abcfafd4de9d5073e6fadfbf3518f5", + "distro": "ae9dabd253b8abcefa5c60049126cc101d7d9067", "author": { "name": "Microsoft Corporation" }, From bad4294784f8d17bda94fcc6a9101f5051755f74 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 7 Dec 2021 17:52:00 -0800 Subject: [PATCH 0457/2210] Enable expand/collapse cell commands to be called programmatically See microsoft/vscode-jupyter#8477 --- .../notebook/browser/contrib/cellCommands/cellCommands.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 0675220094891..03941006678f0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -344,6 +344,10 @@ registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction { }); } + override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + return parseMultiCellExecutionArgs(accessor, ...args); + } + async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise { if (context.ui) { context.cell.isInputCollapsed = true; @@ -372,6 +376,10 @@ registerAction2(class ExpandCellInputAction extends NotebookMultiCellAction { }); } + override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + return parseMultiCellExecutionArgs(accessor, ...args); + } + async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise { if (context.ui) { context.cell.isInputCollapsed = false; From c332de5b0cbb9272335ddccc5a523665e5f08dcd Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 8 Dec 2021 16:45:47 -0500 Subject: [PATCH 0458/2210] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40b41b3d4025a..97be46deada41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.63.0", + "version": "1.64.0", "distro": "ae9dabd253b8abcefa5c60049126cc101d7d9067", "author": { "name": "Microsoft Corporation" From 1341ca3d7f69e794478f172d876170ad91f5b268 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 8 Dec 2021 15:32:14 -0800 Subject: [PATCH 0459/2210] Remove timeouts in search smoketest. Improvements came from waiting for search to complete before clicking buttons, clearing previous results before starting the search, and adding a retry around the clicking. --- test/automation/src/search.ts | 41 ++++++++++++---------- test/smoke/src/areas/search/search.test.ts | 15 ++++---- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/test/automation/src/search.ts b/test/automation/src/search.ts index 247c714ab6b8a..de48c8eae4042 100644 --- a/test/automation/src/search.ts +++ b/test/automation/src/search.ts @@ -33,6 +33,11 @@ export class Search extends Viewlet { super(code); } + async clearSearchResults(): Promise { + await this.code.waitAndClick(`.sidebar .codicon-search-clear-results`); + await this.waitForNoResultText(); + } + async openSearchViewlet(): Promise { if (process.platform === 'darwin') { await this.code.dispatchKeybinding('cmd+shift+f'); @@ -49,6 +54,7 @@ export class Search extends Viewlet { } async searchFor(text: string): Promise { + await this.clearSearchResults(); await this.waitForInputFocus(INPUT); await this.code.waitForSetValue(INPUT, text); await this.submitSearch(); @@ -74,18 +80,16 @@ export class Search extends Viewlet { await this.code.waitAndClick(`${VIEWLET} .query-details.more .more`); } - async removeFileMatch(filename: string): Promise { + async removeFileMatch(filename: string, expectedText: string): Promise { const fileMatch = FILE_MATCH(filename); + // Retry this because the click can fail if the search tree is rerendered at the same time await retry( - () => this.code.waitAndClick(fileMatch), - () => this.code.waitForElement(`${fileMatch} .action-label.codicon-search-remove`, el => !!el && el.top > 0 && el.left > 0, 10) - ); - - // ¯\_(ツ)_/¯ - await new Promise(c => setTimeout(c, 500)); - await this.code.waitAndClick(`${fileMatch} .action-label.codicon-search-remove`); - await this.code.waitForElement(fileMatch, el => !el); + async () => { + await this.code.waitAndClick(fileMatch); + await this.code.waitAndClick(`${fileMatch} .action-label.codicon-search-remove`); + }, + async () => this.waitForResultText(expectedText, 10)); } async expandReplace(): Promise { @@ -100,22 +104,21 @@ export class Search extends Viewlet { await this.code.waitForSetValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox textarea[title="Replace"]`, text); } - async replaceFileMatch(filename: string): Promise { + async replaceFileMatch(filename: string, expectedText: string): Promise { const fileMatch = FILE_MATCH(filename); + // Retry this because the click can fail if the search tree is rerendered at the same time await retry( - () => this.code.waitAndClick(fileMatch), - () => this.code.waitForElement(`${fileMatch} .action-label.codicon.codicon-search-replace-all`, el => !!el && el.top > 0 && el.left > 0, 10) - ); - - // ¯\_(ツ)_/¯ - await new Promise(c => setTimeout(c, 500)); - await this.code.waitAndClick(`${fileMatch} .action-label.codicon.codicon-search-replace-all`); + async () => { + await this.code.waitAndClick(fileMatch); + await this.code.waitAndClick(`${fileMatch} .action-label.codicon.codicon-search-replace-all`); + }, + () => this.waitForResultText(expectedText, 10)); } - async waitForResultText(text: string): Promise { + async waitForResultText(text: string, retryCount?: number): Promise { // The label can end with " - " depending on whether the search editor is enabled - await this.code.waitForTextContent(`${VIEWLET} .messages .message`, undefined, result => result.startsWith(text)); + await this.code.waitForTextContent(`${VIEWLET} .messages .message`, undefined, result => result.startsWith(text), retryCount); } async waitForNoResultText(): Promise { diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 4e3d674110461..34c27d39024ff 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -49,25 +49,26 @@ export function setup(opts: minimist.ParsedArgs) { await app.workbench.search.hideQueryDetails(); }); - it.skip('dismisses result & checks for correct result number', async function () { + it('dismisses result & checks for correct result number', async function () { const app = this.app as Application; await app.workbench.search.searchFor('body'); - await app.workbench.search.removeFileMatch('app.js'); - await app.workbench.search.waitForResultText('12 results in 4 files'); + await app.workbench.search.waitForResultText('16 results in 5 files'); + await app.workbench.search.removeFileMatch('app.js', '12 results in 4 files'); }); - it.skip('replaces first search result with a replace term', async function () { + it('replaces first search result with a replace term', async function () { const app = this.app as Application; await app.workbench.search.searchFor('body'); + await app.workbench.search.waitForResultText('16 results in 5 files'); await app.workbench.search.expandReplace(); await app.workbench.search.setReplaceText('ydob'); - await app.workbench.search.replaceFileMatch('app.js'); - await app.workbench.search.waitForResultText('12 results in 4 files'); + await app.workbench.search.replaceFileMatch('app.js', '12 results in 4 files'); await app.workbench.search.searchFor('ydob'); + await app.workbench.search.waitForResultText('4 results in 1 file'); await app.workbench.search.setReplaceText('body'); - await app.workbench.search.replaceFileMatch('app.js'); + await app.workbench.search.replaceFileMatch('app.js', '0 results in 0 files'); await app.workbench.search.waitForResultText('0 results in 0 files'); }); }); From 68976705968229accd11fdfe1cba36a908208dc9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 8 Dec 2021 16:40:50 -0800 Subject: [PATCH 0460/2210] Reenable notebook integration tests --- .../src/singlefolder-tests/notebook.editor.test.ts | 5 +++-- .../src/singlefolder-tests/notebook.test.ts | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index f2ede850c6a32..8465c3ceaaf3e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import * as utils from '../utils'; -suite.skip('Notebook Editor', function () { +suite('Notebook Editor', function () { const contentSerializer = new class implements vscode.NotebookSerializer { deserializeNotebook() { @@ -77,7 +77,8 @@ suite.skip('Notebook Editor', function () { assert.strictEqual(editor2.viewColumn, vscode.ViewColumn.Two); }); - test('Opening a notebook should fire activeNotebook event changed only once', async function () { + // #138683 + test.skip('Opening a notebook should fire activeNotebook event changed only once', async function () { const openedEditor = utils.asPromise(vscode.window.onDidChangeActiveNotebookEditor); const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const editor = await vscode.window.showNotebookDocument(resource); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 425f0db297927..b4c4c7a3e2b23 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -136,7 +136,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { } }; -suite.skip('Notebook API tests', function () { +suite('Notebook API tests', function () { const testDisposables: vscode.Disposable[] = []; const suiteDisposables: vscode.Disposable[] = []; @@ -872,7 +872,7 @@ suite.skip('Notebook API tests', function () { }); }); -suite.skip('statusbar', () => { +suite('statusbar', () => { const emitter = new vscode.EventEmitter(); const onDidCallProvide = emitter.event; const suiteDisposables: vscode.Disposable[] = []; @@ -910,7 +910,7 @@ suite.skip('statusbar', () => { }); }); -suite.skip('Notebook API tests (metadata)', function () { +suite('Notebook API tests (metadata)', function () { const testDisposables: vscode.Disposable[] = []; const suiteDisposables: vscode.Disposable[] = []; From acc0803f381bd88a3e2f2b27f844fe98116db4d7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Dec 2021 07:03:29 +0100 Subject: [PATCH 0461/2210] cleanup automation --- test/automation/src/application.ts | 6 +++--- test/automation/src/code.ts | 23 ++++++---------------- test/automation/src/electronDriver.ts | 6 ++++-- test/automation/src/playwrightDriver.ts | 21 ++++++++++---------- test/smoke/src/areas/search/search.test.ts | 6 +++--- 5 files changed, 26 insertions(+), 36 deletions(-) diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 0b2bbedee051d..71efdf34059c1 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { Workbench } from './workbench'; -import { Code, spawn, SpawnOptions } from './code'; +import { Code, launch, LaunchOptions } from './code'; import { Logger, measureAndLog } from './logger'; export const enum Quality { @@ -15,7 +15,7 @@ export const enum Quality { Stable } -export interface ApplicationOptions extends SpawnOptions { +export interface ApplicationOptions extends LaunchOptions { quality: Quality; workspacePath: string; waitTime: number; @@ -112,7 +112,7 @@ export class Application { } private async startApplication(extraArgs: string[] = []): Promise { - const code = this._code = await spawn({ + const code = this._code = await launch({ ...this.options, extraArgs: [...(this.options.extraArgs || []), ...extraArgs], }); diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index ac138b770dedd..c2c85b2a263e3 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -14,7 +14,7 @@ import { copyExtension } from './extensions'; const repoPath = path.join(__dirname, '../../..'); -export interface SpawnOptions { +export interface LaunchOptions { codePath?: string; workspacePath: string; userDataDir: string; @@ -26,7 +26,6 @@ export interface SpawnOptions { web?: boolean; headless?: boolean; browser?: 'chromium' | 'webkit' | 'firefox'; - testTitle?: string; } let stopped = false; @@ -34,7 +33,7 @@ process.on('exit', () => stopped = true); process.on('SIGINT', () => stopped = true); process.on('SIGTERM', () => stopped = true); -export async function spawn(options: SpawnOptions): Promise { +export async function launch(options: LaunchOptions): Promise { if (stopped) { throw new Error('Smoke test process has terminated, refusing to spawn Code'); } @@ -43,22 +42,12 @@ export async function spawn(options: SpawnOptions): Promise { // Browser smoke tests if (options.web) { - return spawnBrowser(options); + const { serverProcess, client, driver } = await launchPlaywright(options); + return new Code(client, driver, options.logger, serverProcess); } // Electron smoke tests - return spawnElectron(options); -} - -async function spawnBrowser(options: SpawnOptions): Promise { - const { serverProcess, client, driver } = await launchPlaywright(options.codePath, options.userDataDir, options.extensionsPath, options.workspacePath, Boolean(options.verbose), options, options.logger); - - return new Code(client, driver, options.logger, serverProcess); -} - -async function spawnElectron(options: SpawnOptions): Promise { - const { electronProcess, client, driver } = await launchElectron(options.codePath, options.userDataDir, options.extensionsPath, options.workspacePath, Boolean(options.verbose), Boolean(options.remote), options.extraArgs, options.logger); - + const { electronProcess, client, driver } = await launchElectron(options); return new Code(client, driver, options.logger, electronProcess); } @@ -110,7 +99,7 @@ export class Code { private readonly mainProcess: cp.ChildProcess ) { this.driver = new Proxy(driver, { - get(target, prop, receiver) { + get(target, prop) { if (typeof prop === 'symbol') { throw new Error('Invalid usage'); } diff --git a/test/automation/src/electronDriver.ts b/test/automation/src/electronDriver.ts index 823b4f0fb42c7..fdbac5d6f5369 100644 --- a/test/automation/src/electronDriver.ts +++ b/test/automation/src/electronDriver.ts @@ -13,11 +13,13 @@ import { promisify } from 'util'; import * as kill from 'tree-kill'; import { copyExtension } from './extensions'; import { URI } from 'vscode-uri'; -import { Logger, measureAndLog } from './logger'; +import { measureAndLog } from './logger'; +import type { LaunchOptions } from './code'; const repoPath = path.join(__dirname, '../../..'); -export async function launch(codePath: string | undefined, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, remote: boolean, extraArgs: string[] | undefined, logger: Logger): Promise<{ electronProcess: ChildProcess, client: IDisposable, driver: IDriver }> { +export async function launch(options: LaunchOptions): Promise<{ electronProcess: ChildProcess, client: IDisposable, driver: IDriver }> { + const { codePath, workspacePath, extensionsPath, userDataDir, remote, logger, verbose, extraArgs } = options; const env = { ...process.env }; const logsPath = path.join(repoPath, '.build', 'logs', remote ? 'smoke-tests-remote' : 'smoke-tests'); const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index d85f2b9cfe194..c137bb22486c1 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -13,6 +13,7 @@ import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; import { PageFunction } from 'playwright-core/types/structs'; import { Logger, measureAndLog } from './logger'; +import type { LaunchOptions } from './code'; const width = 1200; const height = 800; @@ -198,29 +199,26 @@ class PlaywrightDriver implements IDriver { let port = 9000; -export interface PlaywrightOptions { - readonly browser?: 'chromium' | 'webkit' | 'firefox'; - readonly headless?: boolean; -} - -export async function launch(codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, userDataDir: string, extensionsPath: string, workspacePath: string, verbose: boolean, options: PlaywrightOptions = {}, logger: Logger): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver }> { +export async function launch(options: LaunchOptions): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver }> { // Launch server - const { serverProcess, endpoint } = await launchServer(userDataDir, codeServerPath, extensionsPath, verbose, logger); + const { serverProcess, endpoint } = await launchServer(options); // Launch browser - const { browser, context, page } = await launchBrowser(options, endpoint, workspacePath, logger); + const { browser, context, page } = await launchBrowser(options, endpoint); return { serverProcess, client: { dispose: () => { /* there is no client to dispose for browser, teardown is triggered via exitApplication call */ } }, - driver: new PlaywrightDriver(serverProcess, browser, context, page, logger) + driver: new PlaywrightDriver(serverProcess, browser, context, page, options.logger) }; } -async function launchServer(userDataDir: string, codeServerPath: string | undefined, extensionsPath: string, verbose: boolean, logger: Logger) { +async function launchServer(options: LaunchOptions) { + const { userDataDir, codePath, extensionsPath, verbose, logger } = options; + const codeServerPath = codePath ?? process.env.VSCODE_REMOTE_SERVER_PATH; const agentFolder = userDataDir; await measureAndLog(promisify(mkdir)(agentFolder), `mkdir(${agentFolder})`, logger); const env = { @@ -274,7 +272,8 @@ async function launchServer(userDataDir: string, codeServerPath: string | undefi }; } -async function launchBrowser(options: PlaywrightOptions, endpoint: string, workspacePath: string, logger: Logger) { +async function launchBrowser(options: LaunchOptions, endpoint: string) { + const { logger, workspacePath } = options; const browser = await measureAndLog(playwright[options.browser ?? 'chromium'].launch({ headless: options.headless ?? false }), 'playwright#launch', logger); const context = await measureAndLog(browser.newContext(), 'browser.newContext', logger); diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 34c27d39024ff..a949894a2e38f 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -73,12 +73,12 @@ export function setup(opts: minimist.ParsedArgs) { }); }); - describe('Quick Access', () => { + describe('Quick Open', () => { // Shared before/after handling installCommonTestHandlers(opts); - it('quick access search produces correct result', async function () { + it('quick open search produces correct result', async function () { const app = this.app as Application; const expectedNames = [ '.eslintrc.json', @@ -95,7 +95,7 @@ export function setup(opts: minimist.ParsedArgs) { await app.code.dispatchKeybinding('escape'); }); - it('quick access respects fuzzy matching', async function () { + it('quick open respects fuzzy matching', async function () { const app = this.app as Application; const expectedNames = [ 'tasks.json', From ec3ba7a32040bb047a9e7ea70325416f5f1db511 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Dec 2021 07:55:25 +0100 Subject: [PATCH 0462/2210] smoke - rewrite killing --- test/automation/src/code.ts | 46 +++++++++++++++++++++---- test/automation/src/electronDriver.ts | 38 ++++++++++---------- test/automation/src/playwrightDriver.ts | 33 +++++++----------- test/integration/browser/src/index.ts | 10 ++++-- 4 files changed, 79 insertions(+), 48 deletions(-) diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index c2c85b2a263e3..1e77d29068502 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -28,10 +28,38 @@ export interface LaunchOptions { browser?: 'chromium' | 'webkit' | 'firefox'; } +interface ICodeInstance { + kill: () => Promise +} + +const instances = new Set(); + +function registerInstance(process: cp.ChildProcess, logger: Logger, type: string, kill: () => Promise) { + const instance = { kill }; + instances.add(instance); + process.once('exit', (code, signal) => { + logger.log(`Process terminated (type: ${type}, pid: ${process.pid}, code: ${code}, signal: ${signal})`); + + instances.delete(instance); + }); +} + +async function teardown(signal?: number) { + stopped = true; + + for (const instance of instances) { + await instance.kill(); + } + + if (typeof signal === 'number') { + process.exit(signal); + } +} + let stopped = false; -process.on('exit', () => stopped = true); -process.on('SIGINT', () => stopped = true); -process.on('SIGTERM', () => stopped = true); +process.on('exit', () => teardown()); +process.on('SIGINT', () => teardown(128 + 2)); // https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events +process.on('SIGTERM', () => teardown(128 + 15)); // same as above export async function launch(options: LaunchOptions): Promise { if (stopped) { @@ -42,13 +70,19 @@ export async function launch(options: LaunchOptions): Promise { // Browser smoke tests if (options.web) { - const { serverProcess, client, driver } = await launchPlaywright(options); + const { serverProcess, client, driver, kill } = await measureAndLog(launchPlaywright(options), 'launch playwright', options.logger); + registerInstance(serverProcess, options.logger, 'server', kill); + return new Code(client, driver, options.logger, serverProcess); } // Electron smoke tests - const { electronProcess, client, driver } = await launchElectron(options); - return new Code(client, driver, options.logger, electronProcess); + else { + const { electronProcess, client, driver, kill } = await measureAndLog(launchElectron(options), 'launch electron', options.logger); + registerInstance(electronProcess, options.logger, 'electron', kill); + + return new Code(client, driver, options.logger, electronProcess); + } } async function poll( diff --git a/test/automation/src/electronDriver.ts b/test/automation/src/electronDriver.ts index fdbac5d6f5369..8bf0b64beb532 100644 --- a/test/automation/src/electronDriver.ts +++ b/test/automation/src/electronDriver.ts @@ -18,7 +18,7 @@ import type { LaunchOptions } from './code'; const repoPath = path.join(__dirname, '../../..'); -export async function launch(options: LaunchOptions): Promise<{ electronProcess: ChildProcess, client: IDisposable, driver: IDriver }> { +export async function launch(options: LaunchOptions): Promise<{ electronProcess: ChildProcess, client: IDisposable, driver: IDriver, kill: () => Promise }> { const { codePath, workspacePath, extensionsPath, userDataDir, remote, logger, verbose, extraArgs } = options; const env = { ...process.env }; const logsPath = path.join(repoPath, '.build', 'logs', remote ? 'smoke-tests-remote' : 'smoke-tests'); @@ -89,30 +89,30 @@ export async function launch(options: LaunchOptions): Promise<{ electronProcess: const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath(); const electronProcess = spawn(electronPath, args, spawnOptions); - if (verbose) { - logger.log(`Started electron for desktop smoke tests on pid ${electronProcess.pid}`); - } - - let electronProcessDidExit = false; - electronProcess.once('exit', (code, signal) => { - if (verbose) { - logger.log(`Electron for desktop smoke tests terminated (pid: ${electronProcess.pid}, code: ${code}, signal: ${signal})`); - } - electronProcessDidExit = true; - }); - - process.once('exit', () => { - if (!electronProcessDidExit) { - electronProcess.kill(); - } - }); + logger.log(`Started electron for desktop smoke tests on pid ${electronProcess.pid}`); let retries = 0; while (true) { try { const { client, driver } = await measureAndLog(connectElectronDriver(outPath, driverIPCHandle), 'connectElectronDriver()', logger); - return { electronProcess, client, driver }; + return { + electronProcess, + client, + driver, + kill: async () => { + try { + return promisify(kill)(electronProcess.pid!); + } catch (error) { + try { + process.kill(electronProcess.pid!, 0); // throws an exception if the process doesn't exist anymore + logger.log(`Error tearing down electron client (pid: ${electronProcess.pid}): ${error}`); + } catch (error) { + return; // Expected when process is gone + } + } + } + }; } catch (err) { // give up diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index c137bb22486c1..ae1770948f59b 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -199,7 +199,7 @@ class PlaywrightDriver implements IDriver { let port = 9000; -export async function launch(options: LaunchOptions): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver }> { +export async function launch(options: LaunchOptions): Promise<{ serverProcess: ChildProcess, client: IDisposable, driver: IDriver, kill: () => Promise }> { // Launch server const { serverProcess, endpoint } = await launchServer(options); @@ -212,12 +212,13 @@ export async function launch(options: LaunchOptions): Promise<{ serverProcess: C client: { dispose: () => { /* there is no client to dispose for browser, teardown is triggered via exitApplication call */ } }, - driver: new PlaywrightDriver(serverProcess, browser, context, page, options.logger) + driver: new PlaywrightDriver(serverProcess, browser, context, page, options.logger), + kill: () => teardown(serverProcess, options.logger) }; } async function launchServer(options: LaunchOptions) { - const { userDataDir, codePath, extensionsPath, verbose, logger } = options; + const { userDataDir, codePath, extensionsPath, logger } = options; const codeServerPath = codePath ?? process.env.VSCODE_REMOTE_SERVER_PATH; const agentFolder = userDataDir; await measureAndLog(promisify(mkdir)(agentFolder), `mkdir(${agentFolder})`, logger); @@ -234,18 +235,14 @@ async function launchServer(options: LaunchOptions) { serverLocation = join(codeServerPath, `server.${process.platform === 'win32' ? 'cmd' : 'sh'}`); args.push(`--logsPath=${logsPath}`); - if (verbose) { - logger.log(`Starting built server from '${serverLocation}'`); - logger.log(`Storing log files into '${logsPath}'`); - } + logger.log(`Starting built server from '${serverLocation}'`); + logger.log(`Storing log files into '${logsPath}'`); } else { serverLocation = join(root, `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`); args.push('--logsPath', logsPath); - if (verbose) { - logger.log(`Starting server out of sources from '${serverLocation}'`); - logger.log(`Storing log files into '${logsPath}'`); - } + logger.log(`Starting server out of sources from '${serverLocation}'`); + logger.log(`Storing log files into '${logsPath}'`); } const serverProcess = spawn( @@ -254,17 +251,11 @@ async function launchServer(options: LaunchOptions) { { env } ); - if (verbose) { - logger.log(`*** Started server for browser smoke tests (pid: ${serverProcess.pid})`); - serverProcess.once('exit', (code, signal) => logger.log(`Server for browser smoke tests terminated (pid: ${serverProcess.pid}, code: ${code}, signal: ${signal})`)); - - serverProcess.stderr?.on('data', error => logger.log(`Server stderr: ${error}`)); - serverProcess.stdout?.on('data', data => logger.log(`Server stdout: ${data}`)); - } + logger.log(`Started server for browser smoke tests (pid: ${serverProcess.pid})`); + serverProcess.once('exit', (code, signal) => logger.log(`Server for browser smoke tests terminated (pid: ${serverProcess.pid}, code: ${code}, signal: ${signal})`)); - process.on('exit', () => teardown(serverProcess, logger)); - process.on('SIGINT', () => teardown(serverProcess, logger)); - process.on('SIGTERM', () => teardown(serverProcess, logger)); + serverProcess.stderr?.on('data', error => logger.log(`Server stderr: ${error}`)); + serverProcess.stdout?.on('data', data => logger.log(`Server stdout: ${data}`)); return { serverProcess, diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 262faad5f0645..df9f5dc8191b6 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -162,8 +162,14 @@ async function launchServer(browserType: BrowserType): Promise<{ endpoint: url.U } process.on('exit', () => serverProcess.kill()); - process.on('SIGINT', () => serverProcess.kill()); - process.on('SIGTERM', () => serverProcess.kill()); + process.on('SIGINT', () => { + serverProcess.kill(); + process.exit(128 + 2); // https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events + }); + process.on('SIGTERM', () => { + serverProcess.kill(); + process.exit(128 + 15); // https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events + }); return new Promise(c => { serverProcess.stdout!.on('data', data => { From 15415b6de68f3fcfefcd264ac37cc3c3d54a378b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Dec 2021 08:03:48 +0100 Subject: [PATCH 0463/2210] smoke - align killing --- .../platform/windows/electron-main/window.ts | 6 +++ test/automation/src/electronDriver.ts | 46 +++++++++++-------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 14c5c498e1f25..1159a1a1342f9 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -595,6 +595,12 @@ export class CodeWindow extends Disposable implements ICodeWindow { return; } + // If we run smoke tests, we never want to show a blocking dialog + if (this.environmentMainService.driverHandle) { + this.destroyWindow(false); + return; + } + // Unresponsive if (type === WindowError.UNRESPONSIVE) { if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) { diff --git a/test/automation/src/electronDriver.ts b/test/automation/src/electronDriver.ts index 8bf0b64beb532..eddfc7965500d 100644 --- a/test/automation/src/electronDriver.ts +++ b/test/automation/src/electronDriver.ts @@ -13,7 +13,7 @@ import { promisify } from 'util'; import * as kill from 'tree-kill'; import { copyExtension } from './extensions'; import { URI } from 'vscode-uri'; -import { measureAndLog } from './logger'; +import { Logger, measureAndLog } from './logger'; import type { LaunchOptions } from './code'; const repoPath = path.join(__dirname, '../../..'); @@ -100,18 +100,7 @@ export async function launch(options: LaunchOptions): Promise<{ electronProcess: electronProcess, client, driver, - kill: async () => { - try { - return promisify(kill)(electronProcess.pid!); - } catch (error) { - try { - process.kill(electronProcess.pid!, 0); // throws an exception if the process doesn't exist anymore - logger.log(`Error tearing down electron client (pid: ${electronProcess.pid}): ${error}`); - } catch (error) { - return; // Expected when process is gone - } - } - } + kill: () => teardown(electronProcess, options.logger) }; } catch (err) { @@ -119,11 +108,7 @@ export async function launch(options: LaunchOptions): Promise<{ electronProcess: if (++retries > 30) { logger.log(`Error connecting driver: ${err}. Giving up...`); - try { - await measureAndLog(promisify(kill)(electronProcess.pid!), 'Kill Electron after failing to connect', logger); - } catch (error) { - logger.log(`Error tearing down electron client (pid: ${electronProcess.pid}): ${error}`); - } + await measureAndLog(teardown(electronProcess, logger), 'Kill Electron after failing to connect', logger); throw err; } @@ -140,6 +125,31 @@ export async function launch(options: LaunchOptions): Promise<{ electronProcess: } } +async function teardown(electronProcess: ChildProcess, logger: Logger): Promise { + const electronPid = electronProcess.pid; + if (typeof electronPid !== 'number') { + return; + } + + let retries = 0; + while (retries < 3) { + retries++; + + try { + return await promisify(kill)(electronPid); + } catch (error) { + try { + process.kill(electronPid, 0); // throws an exception if the process doesn't exist anymore + logger.log(`Error tearing down electron client (pid: ${electronPid}, attempt: ${retries}): ${error}`); + } catch (error) { + return; // Expected when process is gone + } + } + } + + logger.log(`Gave up tearing down electron client after ${retries} attempts...`); +} + function getDevElectronPath(): string { const buildPath = path.join(repoPath, '.build'); const product = require(path.join(repoPath, 'product.json')); From 158bd2507e6317c2270230494a9d61f1ee752899 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Dec 2021 08:48:42 +0100 Subject: [PATCH 0464/2210] smoke - always have a logger --- test/automation/src/application.ts | 2 +- test/smoke/src/areas/editor/editor.test.ts | 7 +- .../src/areas/extensions/extensions.test.ts | 7 +- .../src/areas/languages/languages.test.ts | 7 +- .../src/areas/multiroot/multiroot.test.ts | 21 ++- .../smoke/src/areas/notebook/notebook.test.ts | 7 +- .../src/areas/preferences/preferences.test.ts | 7 +- test/smoke/src/areas/search/search.test.ts | 9 +- .../src/areas/statusbar/statusbar.test.ts | 11 +- .../areas/terminal/terminal-editors.test.ts | 5 +- .../terminal/terminal-persistence.test.ts | 5 +- .../areas/terminal/terminal-profiles.test.ts | 3 +- .../src/areas/terminal/terminal-tabs.test.ts | 6 +- .../smoke/src/areas/terminal/terminal.test.ts | 17 +- .../src/areas/workbench/data-loss.test.ts | 23 ++- test/smoke/src/areas/workbench/launch.test.ts | 11 +- .../src/areas/workbench/localization.test.ts | 11 +- test/smoke/src/main.ts | 150 +++++++++--------- test/smoke/src/utils.ts | 29 ++-- 19 files changed, 163 insertions(+), 175 deletions(-) diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 71efdf34059c1..5799a11949868 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -94,7 +94,7 @@ export class Application { async captureScreenshot(name: string): Promise { if (this.options.screenshotsPath) { - const raw = await measureAndLog(this.code.capturePage(), 'capturePage', this.options.logger); + const raw = await measureAndLog(this.code.capturePage(), 'capturePage', this.logger); const buffer = Buffer.from(raw, 'base64'); const screenshotPath = path.join(this.options.screenshotsPath, `${name}.png`); this.logger.log('Screenshot recorded:', screenshotPath); diff --git a/test/smoke/src/areas/editor/editor.test.ts b/test/smoke/src/areas/editor/editor.test.ts index c57672a04caf0..9e2363d31e5dd 100644 --- a/test/smoke/src/areas/editor/editor.test.ts +++ b/test/smoke/src/areas/editor/editor.test.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); -import { Application } from '../../../../automation'; +import { Application, Logger } from '../../../../automation'; import { installCommonTestHandlers } from '../../utils'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe('Editor', () => { // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); it('shows correct quick outline', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index 47d327ce993f1..df55392a51857 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); -import { Application, Quality } from '../../../../automation'; +import { Application, Logger, Quality } from '../../../../automation'; import { installCommonTestHandlers } from '../../utils'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe('Extensions', () => { // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); it('install and enable vscode-smoketest-check extension', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/languages/languages.test.ts b/test/smoke/src/areas/languages/languages.test.ts index b17e8c776f443..cd15d7798c9c0 100644 --- a/test/smoke/src/areas/languages/languages.test.ts +++ b/test/smoke/src/areas/languages/languages.test.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); -import { Application, ProblemSeverity, Problems } from '../../../../automation/out'; +import { Application, ProblemSeverity, Problems, Logger } from '../../../../automation'; import { installCommonTestHandlers } from '../../utils'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe('Language Features', () => { // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); it('verifies quick outline', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index 06cd52eb2d6aa..5b2634b484f6c 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; -import minimist = require('minimist'); -import * as path from 'path'; -import { Application } from '../../../../automation'; +import { writeFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { Application, Logger } from '../../../../automation'; import { installCommonTestHandlers } from '../../utils'; function toUri(path: string): string { @@ -18,12 +17,12 @@ function toUri(path: string): string { } function createWorkspaceFile(workspacePath: string): string { - const workspaceFilePath = path.join(path.dirname(workspacePath), 'smoketest.code-workspace'); + const workspaceFilePath = join(dirname(workspacePath), 'smoketest.code-workspace'); const workspace = { folders: [ - { path: toUri(path.join(workspacePath, 'public')) }, - { path: toUri(path.join(workspacePath, 'routes')) }, - { path: toUri(path.join(workspacePath, 'views')) } + { path: toUri(join(workspacePath, 'public')) }, + { path: toUri(join(workspacePath, 'routes')) }, + { path: toUri(join(workspacePath, 'views')) } ], settings: { 'workbench.startupEditor': 'none', @@ -31,16 +30,16 @@ function createWorkspaceFile(workspacePath: string): string { } }; - fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, '\t')); + writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, '\t')); return workspaceFilePath; } -export function setup(opts: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe('Multiroot', () => { // Shared before/after handling - installCommonTestHandlers(opts, async opts => { + installCommonTestHandlers(logger, async opts => { const workspacePath = createWorkspaceFile(opts.workspacePath); return { ...opts, workspacePath }; }); diff --git a/test/smoke/src/areas/notebook/notebook.test.ts b/test/smoke/src/areas/notebook/notebook.test.ts index 40941d8603b85..22a398b2d1391 100644 --- a/test/smoke/src/areas/notebook/notebook.test.ts +++ b/test/smoke/src/areas/notebook/notebook.test.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import minimist = require('minimist'); -import { Application } from '../../../../automation'; +import { Application, Logger } from '../../../../automation'; import { installCommonTestHandlers } from '../../utils'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe.skip('Notebooks', () => { // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); afterEach(async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index 6f2aee54287e1..3b42a18728fa7 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); -import { Application, ActivityBarPosition } from '../../../../automation'; +import { Application, ActivityBarPosition, Logger } from '../../../../automation'; import { installCommonTestHandlers } from '../../utils'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe('Preferences', () => { // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); it('turns off editor line numbers and verifies the live change', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index a949894a2e38f..073e4ca22916d 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import minimist = require('minimist'); -import { Application } from '../../../../automation'; +import { Application, Logger } from '../../../../automation'; import { installCommonTestHandlers, retry } from '../../utils'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe('Search', () => { // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); after(function () { const app = this.app as Application; @@ -76,7 +75,7 @@ export function setup(opts: minimist.ParsedArgs) { describe('Quick Open', () => { // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); it('quick open search produces correct result', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index 89e5b6e63568c..3fcba14c26ad7 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); -import { Application, Quality, StatusBarElement } from '../../../../automation'; +import { Application, Quality, StatusBarElement, Logger } from '../../../../automation'; import { installCommonTestHandlers } from '../../utils'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(isWeb: boolean, logger: Logger) { describe('Statusbar', () => { // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); it('verifies presence of all default status bar elements', async function () { const app = this.app as Application; @@ -24,7 +23,7 @@ export function setup(opts: minimist.ParsedArgs) { await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.PROBLEMS_STATUS); await app.workbench.quickaccess.openFile('app.js'); - if (!opts.web) { + if (!isWeb) { // Encoding picker currently hidden in web (only UTF-8 supported) await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS); } @@ -45,7 +44,7 @@ export function setup(opts: minimist.ParsedArgs) { await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); await app.workbench.quickinput.closeQuickInput(); - if (!opts.web) { + if (!isWeb) { // Encoding picker currently hidden in web (only UTF-8 supported) await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); diff --git a/test/smoke/src/areas/terminal/terminal-editors.test.ts b/test/smoke/src/areas/terminal/terminal-editors.test.ts index 1dd76c6a03e15..a1ca53bf3a92d 100644 --- a/test/smoke/src/areas/terminal/terminal-editors.test.ts +++ b/test/smoke/src/areas/terminal/terminal-editors.test.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ParsedArgs } from 'minimist'; -import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation/out'; +import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation'; -export function setup(opts: ParsedArgs) { +export function setup() { describe('Terminal Editors', () => { let terminal: Terminal; diff --git a/test/smoke/src/areas/terminal/terminal-persistence.test.ts b/test/smoke/src/areas/terminal/terminal-persistence.test.ts index 0994a6bb2d96f..b823a7e4ae3b2 100644 --- a/test/smoke/src/areas/terminal/terminal-persistence.test.ts +++ b/test/smoke/src/areas/terminal/terminal-persistence.test.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ParsedArgs } from 'minimist'; -import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation/out'; +import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation'; -export function setup(opts: ParsedArgs) { +export function setup() { describe('Terminal Persistence', () => { // Acquire automation API let terminal: Terminal; diff --git a/test/smoke/src/areas/terminal/terminal-profiles.test.ts b/test/smoke/src/areas/terminal/terminal-profiles.test.ts index 6ea8e5f2af5b1..e626e8c9079b0 100644 --- a/test/smoke/src/areas/terminal/terminal-profiles.test.ts +++ b/test/smoke/src/areas/terminal/terminal-profiles.test.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ParsedArgs } from 'minimist'; import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation'; const CONTRIBUTED_PROFILE_NAME = `JavaScript Debug Terminal`; const ANY_PROFILE_NAME = '^((?!JavaScript Debug Terminal).)*$'; -export function setup(opts: ParsedArgs) { +export function setup() { describe('Terminal Profiles', () => { // Acquire automation API let terminal: Terminal; diff --git a/test/smoke/src/areas/terminal/terminal-tabs.test.ts b/test/smoke/src/areas/terminal/terminal-tabs.test.ts index e4d409759b8e1..7c415b7b1f27d 100644 --- a/test/smoke/src/areas/terminal/terminal-tabs.test.ts +++ b/test/smoke/src/areas/terminal/terminal-tabs.test.ts @@ -3,11 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation'; -import { ParsedArgs } from 'minimist'; -import { Application, Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation/out'; - -export function setup(opts: ParsedArgs) { +export function setup() { describe('Terminal Tabs', () => { // Acquire automation API let terminal: Terminal; diff --git a/test/smoke/src/areas/terminal/terminal.test.ts b/test/smoke/src/areas/terminal/terminal.test.ts index ce9e3a0d705db..7775e65f47b90 100644 --- a/test/smoke/src/areas/terminal/terminal.test.ts +++ b/test/smoke/src/areas/terminal/terminal.test.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); -import { Application, Terminal, TerminalCommandId } from '../../../../automation/out'; +import { Application, Terminal, TerminalCommandId, Logger } from '../../../../automation'; import { installCommonTestHandlers } from '../../utils'; import { setup as setupTerminalEditorsTests } from './terminal-editors.test'; import { setup as setupTerminalPersistenceTests } from './terminal-persistence.test'; import { setup as setupTerminalProfileTests } from './terminal-profiles.test'; import { setup as setupTerminalTabsTests } from './terminal-tabs.test'; -export function setup(opts: minimist.ParsedArgs) { +export function setup(isWeb: boolean, logger: Logger) { describe('Terminal', function () { // TODO: Enable terminal tests for non-web when the desktop driver is moved to playwright - if (!opts.web) { + if (!isWeb) { return; } @@ -22,7 +21,7 @@ export function setup(opts: minimist.ParsedArgs) { this.retries(3); // Shared before/after handling - installCommonTestHandlers(opts); + installCommonTestHandlers(logger); let terminal: Terminal; before(async function () { @@ -42,9 +41,9 @@ export function setup(opts: minimist.ParsedArgs) { await terminal.runCommand(TerminalCommandId.KillAll); }); - setupTerminalEditorsTests(opts); - setupTerminalPersistenceTests(opts); - setupTerminalProfileTests(opts); - setupTerminalTabsTests(opts); + setupTerminalEditorsTests(); + setupTerminalPersistenceTests(); + setupTerminalProfileTests(); + setupTerminalTabsTests(); }); } diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 0521bcb4859aa..5c648c6188529 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -3,20 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Application, ApplicationOptions, Quality } from '../../../../automation/out'; -import { ParsedArgs } from 'minimist'; +import { Application, ApplicationOptions, Logger, Quality } from '../../../../automation'; import { installCommonAfterHandlers, getRandomUserDataDir, startApp, timeout, installCommonBeforeEachHandler } from '../../utils'; -export function setup(opts: ParsedArgs) { +export function setup(stableCodePath: string | undefined, isRemote: boolean, logger: Logger) { describe('Data Loss (insiders -> insiders)', () => { let app: Application | undefined = undefined; - installCommonBeforeEachHandler(); - installCommonAfterHandlers(opts, () => app); + installCommonBeforeEachHandler(logger); + installCommonAfterHandlers(() => app); it('verifies opened editors are restored', async function () { - app = await startApp(opts, this.defaultOptions); + app = await startApp(this.defaultOptions); // Open 3 editors and pin 2 of them await app.workbench.quickaccess.openFile('www'); @@ -51,7 +50,7 @@ export function setup(opts: ParsedArgs) { }); async function testHotExit(restartDelay: number | undefined, autoSave: boolean | undefined) { - app = await startApp(opts, this.defaultOptions); + app = await startApp(this.defaultOptions); if (autoSave) { await app.workbench.settingsEditor.addUserSetting('files.autoSave', '"afterDelay"'); @@ -98,12 +97,11 @@ export function setup(opts: ParsedArgs) { let insidersApp: Application | undefined = undefined; let stableApp: Application | undefined = undefined; - installCommonBeforeEachHandler(); - installCommonAfterHandlers(opts, () => insidersApp ?? stableApp, async () => stableApp?.stop()); + installCommonBeforeEachHandler(logger); + installCommonAfterHandlers(() => insidersApp ?? stableApp, async () => stableApp?.stop()); it('verifies opened editors are restored', async function () { - const stableCodePath = opts['stable-build']; - if (!stableCodePath || opts.remote) { + if (!stableCodePath || isRemote) { this.skip(); } @@ -160,8 +158,7 @@ export function setup(opts: ParsedArgs) { }); async function testHotExit(restartDelay: number | undefined) { - const stableCodePath = opts['stable-build']; - if (!stableCodePath || opts.remote) { + if (!stableCodePath || isRemote) { this.skip(); } diff --git a/test/smoke/src/areas/workbench/launch.test.ts b/test/smoke/src/areas/workbench/launch.test.ts index d9e54876fe9e2..7b790dfcfeff8 100644 --- a/test/smoke/src/areas/workbench/launch.test.ts +++ b/test/smoke/src/areas/workbench/launch.test.ts @@ -3,22 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); import { join } from 'path'; -import { Application } from '../../../../automation'; +import { Application, Logger } from '../../../../automation'; import { installCommonAfterHandlers, installCommonBeforeEachHandler, startApp } from '../../utils'; -export function setup(args: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe('Launch', () => { let app: Application | undefined; - installCommonBeforeEachHandler(); - installCommonAfterHandlers(args, () => app); + installCommonBeforeEachHandler(logger); + installCommonAfterHandlers(() => app); it(`verifies that application launches when user data directory has non-ascii characters`, async function () { const massagedOptions = { ...this.defaultOptions, userDataDir: join(this.defaultOptions.userDataDir, 'ø') }; - app = await startApp(args, massagedOptions); + app = await startApp(massagedOptions); await app.stop(); app = undefined; diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index ebc6841f473aa..b8a1d5b629180 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -3,25 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); -import { Application, Quality } from '../../../../automation'; +import { Application, Quality, Logger } from '../../../../automation'; import { installCommonAfterHandlers, installCommonBeforeEachHandler, startApp } from '../../utils'; -export function setup(args: minimist.ParsedArgs) { +export function setup(logger: Logger) { describe('Localization', () => { let app: Application | undefined = undefined; - installCommonBeforeEachHandler(); - installCommonAfterHandlers(args, () => app); + installCommonBeforeEachHandler(logger); + installCommonAfterHandlers(() => app); it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () { if (this.defaultOptions.quality === Quality.Dev || this.defaultOptions.remote) { return this.skip(); } - app = await startApp(args, this.defaultOptions); + app = await startApp(this.defaultOptions); await app.workbench.extensions.openExtensionsViewlet(); await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', false); diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 20aec66e4d076..5c06fc05ee9db 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -13,7 +13,7 @@ import * as rimraf from 'rimraf'; import * as mkdirp from 'mkdirp'; import * as vscodetest from 'vscode-test'; import fetch from 'node-fetch'; -import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger, measureAndLog } from '../../automation'; +import { Quality, MultiLogger, Logger, ConsoleLogger, FileLogger, measureAndLog } from '../../automation'; import { timeout } from './utils'; import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; @@ -29,24 +29,7 @@ import { setup as setupLocalizationTests } from './areas/workbench/localization. import { setup as setupLaunchTests } from './areas/workbench/launch.test'; import { setup as setupTerminalTests } from './areas/terminal/terminal.test'; -try { - gracefulify(fs); -} catch (error) { - console.error(`Error enabling graceful-fs: ${error}`); -} - -const testDataPath = path.join(os.tmpdir(), 'vscsmoke'); -if (fs.existsSync(testDataPath)) { - rimraf.sync(testDataPath); -} -fs.mkdirSync(testDataPath); -process.once('exit', () => { - try { - rimraf.sync(testDataPath); - } catch { - // noop - } -}); +const repoPath = path.join(__dirname, '..', '..', '..'); const [, , ...args] = process.argv; const opts = minimist(args, { @@ -68,6 +51,53 @@ const opts = minimist(args, { default: { verbose: false } +}) as { + verbose?: boolean, + remote?: boolean, + headless?: boolean, + web?: boolean, + screenshots?: string, + build?: string, + 'stable-build'?: string, + browser?: string, + electronArgs?: string +}; + +const logger = createLogger(); + +function createLogger(): Logger { + const loggers: Logger[] = []; + + // Log to console if verbose + if (opts.verbose) { + loggers.push(new ConsoleLogger()); + } + + // Always log to log file + const logPath = path.join(repoPath, '.build', 'logs', opts.web ? 'smoke-tests-browser' : opts.remote ? 'smoke-tests-remote' : 'smoke-tests'); + mkdirp.sync(logPath); + loggers.push(new FileLogger(path.join(logPath, 'smoke-test-runner.log'))); + + return new MultiLogger(loggers); +} + +try { + gracefulify(fs); +} catch (error) { + logger.log(`Error enabling graceful-fs: ${error}`); +} + +const testDataPath = path.join(os.tmpdir(), 'vscsmoke'); +if (fs.existsSync(testDataPath)) { + rimraf.sync(testDataPath); +} +fs.mkdirSync(testDataPath); +process.once('exit', () => { + try { + rimraf.sync(testDataPath); + } catch { + // noop + } }); const testRepoUrl = 'https://github.com/microsoft/vscode-smoketest-express'; @@ -81,12 +111,10 @@ if (screenshotsPath) { } function fail(errorMessage): void { - console.error(errorMessage); + logger.log(errorMessage); process.exit(1); } -const repoPath = path.join(__dirname, '..', '..', '..'); - let quality: Quality; let version: string | undefined; @@ -169,9 +197,9 @@ if (!opts.web) { } if (opts.remote) { - console.log(`Running desktop remote smoke tests against ${electronPath}`); + logger.log(`Running desktop remote smoke tests against ${electronPath}`); } else { - console.log(`Running desktop smoke tests against ${electronPath}`); + logger.log(`Running desktop smoke tests against ${electronPath}`); } } @@ -185,7 +213,7 @@ else { if (!fs.existsSync(testCodeServerPath)) { fail(`Can't find Code server at ${testCodeServerPath}.`); } else { - console.log(`Running web smoke tests against ${testCodeServerPath}`); + logger.log(`Running web smoke tests against ${testCodeServerPath}`); } } @@ -194,7 +222,7 @@ else { process.env.VSCODE_DEV = '1'; process.env.VSCODE_CLI = '1'; - console.log(`Running web smoke out of sources`); + logger.log(`Running web smoke out of sources`); } if (process.env.VSCODE_DEV === '1') { @@ -206,7 +234,7 @@ else { const userDataDir = path.join(testDataPath, 'd'); -async function setupRepository(logger: Logger): Promise { +async function setupRepository(): Promise { if (opts['test-repo']) { logger.log('Copying test project repository:', opts['test-repo']); rimraf.sync(workspacePath); @@ -234,7 +262,7 @@ async function setupRepository(logger: Logger): Promise { } } -async function ensureStableCode(logger: Logger): Promise { +async function ensureStableCode(): Promise { if (opts.web || !opts['build']) { return; } @@ -282,25 +310,27 @@ async function ensureStableCode(logger: Logger): Promise { opts['stable-build'] = stableCodePath; } -async function setup(logger: Logger): Promise { +async function setup(): Promise { logger.log('Test data:', testDataPath); logger.log('Preparing smoketest setup...'); - await measureAndLog(ensureStableCode(logger), 'ensureStableCode', logger); - await measureAndLog(setupRepository(logger), 'setupRepository', logger); + await measureAndLog(ensureStableCode(), 'ensureStableCode', logger); + await measureAndLog(setupRepository(), 'setupRepository', logger); logger.log('Smoketest setup done!\n'); } -async function createOptions(): Promise { - return { +before(async function () { + this.timeout(2 * 60 * 1000); // allow two minutes for setup + + this.defaultOptions = { quality, codePath: opts.build, workspacePath, userDataDir, extensionsPath, waitTime: parseInt(opts['wait-time'] || '0') || 20, - logger: await createLogger(), + logger, verbose: opts.verbose, screenshotsPath, remote: opts.remote, @@ -309,30 +339,8 @@ async function createOptions(): Promise { browser: opts.browser, extraArgs: (opts.electronArgs || '').split(' ').map(a => a.trim()).filter(a => !!a) }; -} - - -async function createLogger(): Promise { - const loggers: Logger[] = []; - - // Log to console if verbose - if (opts.verbose) { - loggers.push(new ConsoleLogger()); - } - - // Always log to log file - const logPath = path.join(repoPath, '.build', 'logs', opts.web ? 'smoke-tests-browser' : opts.remote ? 'smoke-tests-remote' : 'smoke-tests'); - await mkdirp(logPath); - loggers.push(new FileLogger(path.join(logPath, 'smoke-test-runner.log'))); - - return new MultiLogger(loggers); -} - -before(async function () { - this.timeout(2 * 60 * 1000); // allow two minutes for setup - const options = this.defaultOptions = await createOptions(); - await setup(options.logger); + await setup(); }); after(async function () { @@ -363,23 +371,23 @@ after(async function () { throw new Error('giving up after 30s'); } }) - ]), 'rimraf(testDataPath)', this.defaultOptions.logger); + ]), 'rimraf(testDataPath)', logger); } catch (error) { - this.defaultOptions.logger(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`); + logger.log(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`); } }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { - if (!opts.web) { setupDataLossTests(opts); } - if (!opts.web) { setupPreferencesTests(opts); } - setupSearchTests(opts); - setupNotebookTests(opts); - setupLanguagesTests(opts); - setupEditorTests(opts); - setupTerminalTests(opts); - setupStatusbarTests(opts); - setupExtensionTests(opts); - if (!opts.web) { setupMultirootTests(opts); } - if (!opts.web) { setupLocalizationTests(opts); } - if (!opts.web) { setupLaunchTests(opts); } + if (!opts.web) { setupDataLossTests(opts['stable-build'], !!opts.remote, logger); } + if (!opts.web) { setupPreferencesTests(logger); } + setupSearchTests(logger); + setupNotebookTests(logger); + setupLanguagesTests(logger); + setupEditorTests(logger); + setupTerminalTests(!!opts.web, logger); + setupStatusbarTests(!!opts.web, logger); + setupExtensionTests(logger); + if (!opts.web) { setupMultirootTests(logger); } + if (!opts.web) { setupLocalizationTests(logger); } + if (!opts.web) { setupLaunchTests(logger); } }); diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index d4d8eabe63466..4ed34926a381e 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist = require('minimist'); import { Suite, Context } from 'mocha'; -import { Application, ApplicationOptions } from '../../automation'; +import { Application, ApplicationOptions, Logger } from '../../automation'; export function describeRepeat(n: number, description: string, callback: (this: Suite) => void): void { for (let i = 0; i < n; i++) { @@ -19,31 +18,31 @@ export function itRepeat(n: number, description: string, callback: (this: Contex } } -export function installCommonTestHandlers(args: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { - installCommonBeforeHandlers(args, optionsTransform); - installCommonAfterHandlers(args); +export function installCommonTestHandlers(logger: Logger, optionsTransform?: (opts: ApplicationOptions) => Promise) { + installCommonBeforeHandlers(logger, optionsTransform); + installCommonAfterHandlers(); } -export function installCommonBeforeHandlers(args: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise) { +export function installCommonBeforeHandlers(logger: Logger, optionsTransform?: (opts: ApplicationOptions) => Promise) { before(async function () { - this.app = await startApp(args, this.defaultOptions, optionsTransform); + this.app = await startApp(this.defaultOptions, optionsTransform); }); - installCommonBeforeEachHandler(); + installCommonBeforeEachHandler(logger); } -export function installCommonBeforeEachHandler() { +export function installCommonBeforeEachHandler(logger: Logger) { beforeEach(async function () { const testTitle = this.currentTest?.title; - this.defaultOptions.logger.log(''); - this.defaultOptions.logger.log(`>>> Test start: ${testTitle} <<<`); - this.defaultOptions.logger.log(''); + logger.log(''); + logger.log(`>>> Test start: ${testTitle} <<<`); + logger.log(''); await this.app?.startTracing(testTitle); }); } -export async function startApp(args: minimist.ParsedArgs, options: ApplicationOptions, optionsTransform?: (opts: ApplicationOptions) => Promise): Promise { +export async function startApp(options: ApplicationOptions, optionsTransform?: (opts: ApplicationOptions) => Promise): Promise { if (optionsTransform) { options = await optionsTransform({ ...options }); } @@ -68,11 +67,11 @@ export function getRandomUserDataDir(options: ApplicationOptions): string { return options.userDataDir.concat(`-${userDataPathSuffix}`); } -export function installCommonAfterHandlers(opts: minimist.ParsedArgs, appFn?: () => Application | undefined, joinFn?: () => Promise) { +export function installCommonAfterHandlers(appFn?: () => Application | undefined, joinFn?: () => Promise) { after(async function () { const app: Application = appFn?.() ?? this.app; - if (this.currentTest?.state === 'failed' && opts.screenshots) { + if (this.currentTest?.state === 'failed') { const name = this.currentTest!.fullTitle().replace(/[^a-z0-9\-]/ig, '_'); try { await app.captureScreenshot(name); From ae0ee547ba6cff3ea54fbd8dce98f9c264ce050b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Dec 2021 09:33:51 +0100 Subject: [PATCH 0465/2210] smoke - reimplement test handlers --- test/smoke/src/areas/editor/editor.test.ts | 4 +- .../src/areas/extensions/extensions.test.ts | 5 +- .../src/areas/languages/languages.test.ts | 4 +- .../src/areas/multiroot/multiroot.test.ts | 4 +- .../smoke/src/areas/notebook/notebook.test.ts | 4 +- .../src/areas/preferences/preferences.test.ts | 5 +- test/smoke/src/areas/search/search.test.ts | 6 +- .../src/areas/statusbar/statusbar.test.ts | 9 +- .../smoke/src/areas/terminal/terminal.test.ts | 4 +- .../src/areas/workbench/data-loss.test.ts | 12 +-- test/smoke/src/areas/workbench/launch.test.ts | 17 ++-- .../src/areas/workbench/localization.test.ts | 18 ++-- test/smoke/src/utils.ts | 89 +++++++++++-------- 13 files changed, 88 insertions(+), 93 deletions(-) diff --git a/test/smoke/src/areas/editor/editor.test.ts b/test/smoke/src/areas/editor/editor.test.ts index 9e2363d31e5dd..0554680c11b6f 100644 --- a/test/smoke/src/areas/editor/editor.test.ts +++ b/test/smoke/src/areas/editor/editor.test.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Application, Logger } from '../../../../automation'; -import { installCommonTestHandlers } from '../../utils'; +import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { describe('Editor', () => { // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); it('shows correct quick outline', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index df55392a51857..d3b6f0b8591b9 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -4,17 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { Application, Logger, Quality } from '../../../../automation'; -import { installCommonTestHandlers } from '../../utils'; +import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { describe('Extensions', () => { // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); it('install and enable vscode-smoketest-check extension', async function () { const app = this.app as Application; - if (app.quality === Quality.Dev) { this.skip(); } diff --git a/test/smoke/src/areas/languages/languages.test.ts b/test/smoke/src/areas/languages/languages.test.ts index cd15d7798c9c0..fd0f64e977e9b 100644 --- a/test/smoke/src/areas/languages/languages.test.ts +++ b/test/smoke/src/areas/languages/languages.test.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Application, ProblemSeverity, Problems, Logger } from '../../../../automation'; -import { installCommonTestHandlers } from '../../utils'; +import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { describe('Language Features', () => { // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); it('verifies quick outline', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index 5b2634b484f6c..51cb7c9c1db3d 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -6,7 +6,7 @@ import { writeFileSync } from 'fs'; import { join, dirname } from 'path'; import { Application, Logger } from '../../../../automation'; -import { installCommonTestHandlers } from '../../utils'; +import { installAllHandlers } from '../../utils'; function toUri(path: string): string { if (process.platform === 'win32') { @@ -39,7 +39,7 @@ export function setup(logger: Logger) { describe('Multiroot', () => { // Shared before/after handling - installCommonTestHandlers(logger, async opts => { + installAllHandlers(logger, opts => { const workspacePath = createWorkspaceFile(opts.workspacePath); return { ...opts, workspacePath }; }); diff --git a/test/smoke/src/areas/notebook/notebook.test.ts b/test/smoke/src/areas/notebook/notebook.test.ts index 22a398b2d1391..5967de3cc0828 100644 --- a/test/smoke/src/areas/notebook/notebook.test.ts +++ b/test/smoke/src/areas/notebook/notebook.test.ts @@ -5,13 +5,13 @@ import * as cp from 'child_process'; import { Application, Logger } from '../../../../automation'; -import { installCommonTestHandlers } from '../../utils'; +import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { describe.skip('Notebooks', () => { // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); afterEach(async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index 3b42a18728fa7..5c955335848ff 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -4,17 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { Application, ActivityBarPosition, Logger } from '../../../../automation'; -import { installCommonTestHandlers } from '../../utils'; +import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { describe('Preferences', () => { // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); it('turns off editor line numbers and verifies the live change', async function () { const app = this.app as Application; - await app.workbench.quickaccess.openFile('app.js'); await app.code.waitForElements('.line-numbers', false, elements => !!elements.length); diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 073e4ca22916d..83546ff24a82d 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -5,13 +5,13 @@ import * as cp from 'child_process'; import { Application, Logger } from '../../../../automation'; -import { installCommonTestHandlers, retry } from '../../utils'; +import { installAllHandlers, retry } from '../../utils'; export function setup(logger: Logger) { describe('Search', () => { // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); after(function () { const app = this.app as Application; @@ -75,7 +75,7 @@ export function setup(logger: Logger) { describe('Quick Open', () => { // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); it('quick open search produces correct result', async function () { const app = this.app as Application; diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index 3fcba14c26ad7..048f1efc1f715 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -4,17 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { Application, Quality, StatusBarElement, Logger } from '../../../../automation'; -import { installCommonTestHandlers } from '../../utils'; +import { installAllHandlers } from '../../utils'; export function setup(isWeb: boolean, logger: Logger) { describe('Statusbar', () => { // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); it('verifies presence of all default status bar elements', async function () { const app = this.app as Application; - await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.BRANCH_STATUS); if (app.quality !== Quality.Dev) { await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.FEEDBACK_ICON); @@ -35,7 +34,6 @@ export function setup(isWeb: boolean, logger: Logger) { it(`verifies that 'quick input' opens when clicking on status bar elements`, async function () { const app = this.app as Application; - await app.workbench.statusbar.clickOn(StatusBarElement.BRANCH_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); await app.workbench.quickinput.closeQuickInput(); @@ -60,14 +58,12 @@ export function setup(isWeb: boolean, logger: Logger) { it(`verifies that 'Problems View' appears when clicking on 'Problems' status element`, async function () { const app = this.app as Application; - await app.workbench.statusbar.clickOn(StatusBarElement.PROBLEMS_STATUS); await app.workbench.problems.waitForProblemsView(); }); it(`verifies if changing EOL is reflected in the status bar`, async function () { const app = this.app as Application; - await app.workbench.quickaccess.openFile('app.js'); await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS); @@ -79,7 +75,6 @@ export function setup(isWeb: boolean, logger: Logger) { it(`verifies that 'Tweet us feedback' pop-up appears when clicking on 'Feedback' icon`, async function () { const app = this.app as Application; - if (app.quality === Quality.Dev) { return this.skip(); } diff --git a/test/smoke/src/areas/terminal/terminal.test.ts b/test/smoke/src/areas/terminal/terminal.test.ts index 7775e65f47b90..9f0ed731ff01b 100644 --- a/test/smoke/src/areas/terminal/terminal.test.ts +++ b/test/smoke/src/areas/terminal/terminal.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Application, Terminal, TerminalCommandId, Logger } from '../../../../automation'; -import { installCommonTestHandlers } from '../../utils'; +import { installAllHandlers } from '../../utils'; import { setup as setupTerminalEditorsTests } from './terminal-editors.test'; import { setup as setupTerminalPersistenceTests } from './terminal-persistence.test'; import { setup as setupTerminalProfileTests } from './terminal-profiles.test'; @@ -21,7 +21,7 @@ export function setup(isWeb: boolean, logger: Logger) { this.retries(3); // Shared before/after handling - installCommonTestHandlers(logger); + installAllHandlers(logger); let terminal: Terminal; before(async function () { diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 5c648c6188529..0b9a48c5b9ac2 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -4,15 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { Application, ApplicationOptions, Logger, Quality } from '../../../../automation'; -import { installCommonAfterHandlers, getRandomUserDataDir, startApp, timeout, installCommonBeforeEachHandler } from '../../utils'; +import { getRandomUserDataDir, startApp, timeout, installDiagnosticsHandler, installAppAfterHandler } from '../../utils'; export function setup(stableCodePath: string | undefined, isRemote: boolean, logger: Logger) { describe('Data Loss (insiders -> insiders)', () => { let app: Application | undefined = undefined; - installCommonBeforeEachHandler(logger); - installCommonAfterHandlers(() => app); + // Shared before/after handling + installDiagnosticsHandler(logger); + installAppAfterHandler(() => app); it('verifies opened editors are restored', async function () { app = await startApp(this.defaultOptions); @@ -97,8 +98,9 @@ export function setup(stableCodePath: string | undefined, isRemote: boolean, log let insidersApp: Application | undefined = undefined; let stableApp: Application | undefined = undefined; - installCommonBeforeEachHandler(logger); - installCommonAfterHandlers(() => insidersApp ?? stableApp, async () => stableApp?.stop()); + // Shared before/after handling + installDiagnosticsHandler(logger); + installAppAfterHandler(() => insidersApp ?? stableApp, async () => stableApp?.stop()); it('verifies opened editors are restored', async function () { if (!stableCodePath || isRemote) { diff --git a/test/smoke/src/areas/workbench/launch.test.ts b/test/smoke/src/areas/workbench/launch.test.ts index 7b790dfcfeff8..25383a4232986 100644 --- a/test/smoke/src/areas/workbench/launch.test.ts +++ b/test/smoke/src/areas/workbench/launch.test.ts @@ -5,22 +5,17 @@ import { join } from 'path'; import { Application, Logger } from '../../../../automation'; -import { installCommonAfterHandlers, installCommonBeforeEachHandler, startApp } from '../../utils'; +import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { describe('Launch', () => { - let app: Application | undefined; + // Shared before/after handling + installAllHandlers(logger, opts => ({ ...opts, userDataDir: join(opts.userDataDir, 'ø') })); - installCommonBeforeEachHandler(logger); - installCommonAfterHandlers(() => app); - - it(`verifies that application launches when user data directory has non-ascii characters`, async function () { - const massagedOptions = { ...this.defaultOptions, userDataDir: join(this.defaultOptions.userDataDir, 'ø') }; - app = await startApp(massagedOptions); - - await app.stop(); - app = undefined; + it('verifies that application launches when user data directory has non-ascii characters', async function () { + const app = this.app as Application; + await app.workbench.explorer.openExplorerView(); }); }); } diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index b8a1d5b629180..fe6343972fc62 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -3,25 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Application, Quality, Logger } from '../../../../automation'; -import { installCommonAfterHandlers, installCommonBeforeEachHandler, startApp } from '../../utils'; +import { Quality, Logger, Application } from '../../../../automation'; +import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { describe('Localization', () => { - let app: Application | undefined = undefined; + // Shared before/after handling + installAllHandlers(logger); - installCommonBeforeEachHandler(logger); - installCommonAfterHandlers(() => app); - - it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () { + it('starts with "DE" locale and verifies title and viewlets text is in German', async function () { if (this.defaultOptions.quality === Quality.Dev || this.defaultOptions.remote) { return this.skip(); } - app = await startApp(this.defaultOptions); - + const app = this.app as Application; await app.workbench.extensions.openExtensionsViewlet(); await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', false); await app.restart({ extraArgs: ['--locale=DE'] }); @@ -29,9 +26,6 @@ export function setup(logger: Logger) { const result = await app.workbench.localization.getLocalizedStrings(); const localeInfo = await app.workbench.localization.getLocaleInfo(); - await app.stop(); - app = undefined; - if (localeInfo.locale === undefined || localeInfo.locale.toLowerCase() !== 'de') { throw new Error(`The requested locale for VS Code was not German. The received value is: ${localeInfo.locale === undefined ? 'not set' : localeInfo.locale}`); } diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index 4ed34926a381e..4a9721ffda992 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -18,33 +18,71 @@ export function itRepeat(n: number, description: string, callback: (this: Contex } } -export function installCommonTestHandlers(logger: Logger, optionsTransform?: (opts: ApplicationOptions) => Promise) { - installCommonBeforeHandlers(logger, optionsTransform); - installCommonAfterHandlers(); +export function installAllHandlers(logger: Logger, optionsTransform?: (opts: ApplicationOptions) => ApplicationOptions) { + installDiagnosticsHandler(logger); + installAppBeforeHandler(optionsTransform); + installAppAfterHandler(); } -export function installCommonBeforeHandlers(logger: Logger, optionsTransform?: (opts: ApplicationOptions) => Promise) { +export function installDiagnosticsHandler(logger: Logger) { + + // Before each suite before(async function () { - this.app = await startApp(this.defaultOptions, optionsTransform); + const suiteTitle = this.currentTest?.parent?.title; + logger.log(''); + logger.log(`>>> Suite start: '${suiteTitle ?? 'unknown'}' <<<`); + logger.log(''); }); - installCommonBeforeEachHandler(logger); -} - -export function installCommonBeforeEachHandler(logger: Logger) { + // Before each test beforeEach(async function () { const testTitle = this.currentTest?.title; logger.log(''); - logger.log(`>>> Test start: ${testTitle} <<<`); + logger.log(`>>> Test start: '${testTitle}' <<<`); logger.log(''); await this.app?.startTracing(testTitle); }); + + // After each test + afterEach(async function () { + const failed = this.currentTest?.state === 'failed'; + + const testTitle = this.currentTest?.title; + logger.log(''); + if (failed) { + logger.log(`>>> !!! FAILURE !!! Test end: '${testTitle}' !!! FAILURE !!! <<<`); + } else { + logger.log(`>>> Test end: '${testTitle}' <<<`); + } + logger.log(''); + + await this.app?.stopTracing(this.currentTest?.title, failed); + }); +} + +function installAppBeforeHandler(optionsTransform?: (opts: ApplicationOptions) => ApplicationOptions) { + before(async function () { + this.app = await startApp(this.defaultOptions, optionsTransform); + }); +} + +export function installAppAfterHandler(appFn?: () => Application | undefined, joinFn?: () => Promise) { + after(async function () { + const app: Application = appFn?.() ?? this.app; + if (app) { + await app.stop(); + } + + if (joinFn) { + await joinFn(); + } + }); } -export async function startApp(options: ApplicationOptions, optionsTransform?: (opts: ApplicationOptions) => Promise): Promise { +export async function startApp(options: ApplicationOptions, optionsTransform?: (opts: ApplicationOptions) => ApplicationOptions): Promise { if (optionsTransform) { - options = await optionsTransform({ ...options }); + options = optionsTransform({ ...options }); } const app = new Application({ @@ -67,33 +105,6 @@ export function getRandomUserDataDir(options: ApplicationOptions): string { return options.userDataDir.concat(`-${userDataPathSuffix}`); } -export function installCommonAfterHandlers(appFn?: () => Application | undefined, joinFn?: () => Promise) { - after(async function () { - const app: Application = appFn?.() ?? this.app; - - if (this.currentTest?.state === 'failed') { - const name = this.currentTest!.fullTitle().replace(/[^a-z0-9\-]/ig, '_'); - try { - await app.captureScreenshot(name); - } catch (error) { - // ignore - } - } - - if (app) { - await app.stop(); - } - - if (joinFn) { - await joinFn(); - } - }); - - afterEach(async function () { - await this.app?.stopTracing(this.currentTest?.title, this.currentTest?.state === 'failed'); - }); -} - export function timeout(i: number) { return new Promise(resolve => { setTimeout(() => { From 27e38a8e2714b061c1e1647c553ab017f41b99a4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Dec 2021 09:51:55 +0100 Subject: [PATCH 0466/2210] smoke - always create screenshot on failure on desktop --- .../darwin/product-build-darwin.yml | 4 ++-- .../linux/product-build-linux.yml | 4 ++-- .../win32/product-build-win32.yml | 4 ++-- src/vs/platform/driver/electron-main/driver.ts | 17 +++++++++++++++-- test/automation/src/application.ts | 16 +--------------- test/smoke/README.md | 1 - .../src/areas/workbench/data-loss.test.ts | 4 ++-- test/smoke/src/main.ts | 8 -------- test/smoke/src/utils.ts | 18 ++++++++++++------ 9 files changed, 36 insertions(+), 40 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 5ca9eeea492ee..960282ec8fc2e 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -223,7 +223,7 @@ steps: set -e APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) APP_NAME="`ls $APP_ROOT | head -n 1`" - yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests + yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" timeoutInMinutes: 10 displayName: Run smoke tests (Electron) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -233,7 +233,7 @@ steps: APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) APP_NAME="`ls $APP_ROOT | head -n 1`" VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \ - yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --remote --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests-remote + yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --remote timeoutInMinutes: 10 displayName: Run smoke tests (Remote) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 2a6128133097a..14fa3121127ed 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -214,7 +214,7 @@ steps: - script: | set -e APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - yarn smoketest-no-compile --build "$APP_PATH" --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests + yarn smoketest-no-compile --build "$APP_PATH" --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" timeoutInMinutes: 10 displayName: Run smoke tests (Electron) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -223,7 +223,7 @@ steps: set -e APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests-remote + yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" timeoutInMinutes: 10 displayName: Run smoke tests (Remote) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index c9b28addafd2d..a8c937858da92 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -210,7 +210,7 @@ steps: . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --build "$AppRoot" --screenshots $(Build.SourcesDirectory)\.build\logs\smoke-tests } + exec { yarn smoketest-no-compile --build "$AppRoot" } displayName: Run smoke tests (Electron) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) @@ -220,7 +220,7 @@ steps: $ErrorActionPreference = "Stop" $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --build "$AppRoot" --remote --screenshots $(Build.SourcesDirectory)\.build\logs\smoke-tests-remote } + exec { yarn smoketest-no-compile --build "$AppRoot" --remote } displayName: Run smoke tests (Remote) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 17d85fa459225..2c3ecb669ab32 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -20,6 +20,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { join } from 'vs/base/common/path'; +import { VSBuffer } from 'vs/base/common/buffer'; function isSilentKeyCode(keyCode: KeyCode) { return keyCode < KeyCode.Digit0; @@ -37,7 +41,9 @@ export class Driver implements IDriver, IWindowDriverRegistry { private windowServer: IPCServer, private options: IDriverOptions, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, + @IFileService private readonly fileService: IFileService, + @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService ) { } async registerWindowDriver(windowId: number): Promise { @@ -74,7 +80,14 @@ export class Driver implements IDriver, IWindowDriverRegistry { } async stopTracing(windowId: number, name: string, persist: boolean): Promise { - // ignore - tracing is not implemented yet + if (!persist) { + return; + } + + const raw = await this.capturePage(windowId); + const buffer = Buffer.from(raw, 'base64'); + + await this.fileService.writeFile(URI.file(join(this.environmentMainService.logsPath, `${name}.png`)), VSBuffer.wrap(buffer)); } async reloadWindow(windowId: number): Promise { diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 5799a11949868..39024dd49e91b 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -3,11 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; -import * as path from 'path'; import { Workbench } from './workbench'; import { Code, launch, LaunchOptions } from './code'; -import { Logger, measureAndLog } from './logger'; +import { Logger } from './logger'; export const enum Quality { Dev, @@ -19,7 +17,6 @@ export interface ApplicationOptions extends LaunchOptions { quality: Quality; workspacePath: string; waitTime: number; - screenshotsPath: string | null; } export class Application { @@ -92,17 +89,6 @@ export class Application { } } - async captureScreenshot(name: string): Promise { - if (this.options.screenshotsPath) { - const raw = await measureAndLog(this.code.capturePage(), 'capturePage', this.logger); - const buffer = Buffer.from(raw, 'base64'); - const screenshotPath = path.join(this.options.screenshotsPath, `${name}.png`); - this.logger.log('Screenshot recorded:', screenshotPath); - - fs.writeFileSync(screenshotPath, buffer); - } - } - async startTracing(name: string): Promise { await this._code?.startTracing(name); } diff --git a/test/smoke/README.md b/test/smoke/README.md index 84c2030a336eb..9d95d597675b7 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -61,7 +61,6 @@ xattr -d com.apple.quarantine - `--verbose` logs all the low level driver calls made to Code; - `-f PATTERN` (alias `-g PATTERN`) filters the tests to be run. You can also use pretty much any mocha argument; -- `--screenshots SCREENSHOT_DIR` captures screenshots when tests fail. - `--headless` will run playwright in headless mode when `--web` is used. **Note**: you can enable verbose logging of playwright library by setting a `DEBUG` environment variable before running the tests (https://playwright.dev/docs/debug#verbose-api-logs) diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 0b9a48c5b9ac2..cda909d1f3f41 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -12,7 +12,7 @@ export function setup(stableCodePath: string | undefined, isRemote: boolean, log let app: Application | undefined = undefined; // Shared before/after handling - installDiagnosticsHandler(logger); + installDiagnosticsHandler(logger, () => app); installAppAfterHandler(() => app); it('verifies opened editors are restored', async function () { @@ -99,7 +99,7 @@ export function setup(stableCodePath: string | undefined, isRemote: boolean, log let stableApp: Application | undefined = undefined; // Shared before/after handling - installDiagnosticsHandler(logger); + installDiagnosticsHandler(logger, () => insidersApp ?? stableApp); installAppAfterHandler(() => insidersApp ?? stableApp, async () => stableApp?.stop()); it('verifies opened editors are restored', async function () { diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 5c06fc05ee9db..4dc35e3a1d028 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -39,7 +39,6 @@ const opts = minimist(args, { 'stable-build', 'wait-time', 'test-repo', - 'screenshots', 'electronArgs' ], boolean: [ @@ -56,7 +55,6 @@ const opts = minimist(args, { remote?: boolean, headless?: boolean, web?: boolean, - screenshots?: string, build?: string, 'stable-build'?: string, browser?: string, @@ -105,11 +103,6 @@ const workspacePath = path.join(testDataPath, 'vscode-smoketest-express'); const extensionsPath = path.join(testDataPath, 'extensions-dir'); mkdirp.sync(extensionsPath); -const screenshotsPath = opts.screenshots ? path.resolve(opts.screenshots) : null; -if (screenshotsPath) { - mkdirp.sync(screenshotsPath); -} - function fail(errorMessage): void { logger.log(errorMessage); process.exit(1); @@ -332,7 +325,6 @@ before(async function () { waitTime: parseInt(opts['wait-time'] || '0') || 20, logger, verbose: opts.verbose, - screenshotsPath, remote: opts.remote, web: opts.web, headless: opts.headless, diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index 4a9721ffda992..4d3fefc49a662 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -24,7 +24,7 @@ export function installAllHandlers(logger: Logger, optionsTransform?: (opts: App installAppAfterHandler(); } -export function installDiagnosticsHandler(logger: Logger) { +export function installDiagnosticsHandler(logger: Logger, appFn?: () => Application | undefined) { // Before each suite before(async function () { @@ -38,17 +38,22 @@ export function installDiagnosticsHandler(logger: Logger) { beforeEach(async function () { const testTitle = this.currentTest?.title; logger.log(''); - logger.log(`>>> Test start: '${testTitle}' <<<`); + logger.log(`>>> Test start: '${testTitle ?? 'unknown'}' <<<`); logger.log(''); - await this.app?.startTracing(testTitle); + const app: Application = appFn?.() ?? this.app; + await app?.startTracing(testTitle ?? 'unknown'); }); // After each test afterEach(async function () { - const failed = this.currentTest?.state === 'failed'; + const currentTest = this.currentTest; + if (!currentTest) { + return; + } - const testTitle = this.currentTest?.title; + const failed = currentTest.state === 'failed'; + const testTitle = currentTest.title; logger.log(''); if (failed) { logger.log(`>>> !!! FAILURE !!! Test end: '${testTitle}' !!! FAILURE !!! <<<`); @@ -57,7 +62,8 @@ export function installDiagnosticsHandler(logger: Logger) { } logger.log(''); - await this.app?.stopTracing(this.currentTest?.title, failed); + const app: Application = appFn?.() ?? this.app; + await app?.stopTracing(testTitle.replace(/[^a-z0-9\-]/ig, '_'), failed); }); } From 9c7387bb5aaee5912abea51d728fdb46637c1b69 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Dec 2021 11:05:41 +0100 Subject: [PATCH 0467/2210] smoke - bring back stable smoke tests --- test/smoke/src/areas/workbench/data-loss.test.ts | 8 +++++--- test/smoke/src/main.ts | 13 ++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index cda909d1f3f41..8b3b74f50f66e 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -6,7 +6,7 @@ import { Application, ApplicationOptions, Logger, Quality } from '../../../../automation'; import { getRandomUserDataDir, startApp, timeout, installDiagnosticsHandler, installAppAfterHandler } from '../../utils'; -export function setup(stableCodePath: string | undefined, isRemote: boolean, logger: Logger) { +export function setup(ensureStableCode: () => string | undefined, logger: Logger) { describe('Data Loss (insiders -> insiders)', () => { let app: Application | undefined = undefined; @@ -103,7 +103,8 @@ export function setup(stableCodePath: string | undefined, isRemote: boolean, log installAppAfterHandler(() => insidersApp ?? stableApp, async () => stableApp?.stop()); it('verifies opened editors are restored', async function () { - if (!stableCodePath || isRemote) { + const stableCodePath = ensureStableCode(); + if (!stableCodePath) { this.skip(); } @@ -160,7 +161,8 @@ export function setup(stableCodePath: string | undefined, isRemote: boolean, log }); async function testHotExit(restartDelay: number | undefined) { - if (!stableCodePath || isRemote) { + const stableCodePath = ensureStableCode(); + if (!stableCodePath) { this.skip(); } diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 4dc35e3a1d028..15876f9a60baf 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -256,10 +256,6 @@ async function setupRepository(): Promise { } async function ensureStableCode(): Promise { - if (opts.web || !opts['build']) { - return; - } - let stableCodePath = opts['stable-build']; if (!stableCodePath) { const { major, minor } = parseVersion(version!); @@ -304,10 +300,13 @@ async function ensureStableCode(): Promise { } async function setup(): Promise { - logger.log('Test data:', testDataPath); + logger.log('Test data path:', testDataPath); logger.log('Preparing smoketest setup...'); - await measureAndLog(ensureStableCode(), 'ensureStableCode', logger); + if (!opts.web && !opts.remote && opts.build) { + // only enabled when running with --build and not in web or remote + await measureAndLog(ensureStableCode(), 'ensureStableCode', logger); + } await measureAndLog(setupRepository(), 'setupRepository', logger); logger.log('Smoketest setup done!\n'); @@ -370,7 +369,7 @@ after(async function () { }); describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { - if (!opts.web) { setupDataLossTests(opts['stable-build'], !!opts.remote, logger); } + if (!opts.web) { setupDataLossTests(() => opts['stable-build'] /* Do not change, deferred for a reason! */, logger); } if (!opts.web) { setupPreferencesTests(logger); } setupSearchTests(logger); setupNotebookTests(logger); From afaa9ac84e8a3551b1731ae21162ccdf1de88acd Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Dec 2021 16:28:42 +0100 Subject: [PATCH 0468/2210] More adoption of language configuration service. --- .../editor/browser/widget/codeEditorWidget.ts | 7 +- .../widget/embeddedCodeEditorWidget.ts | 6 +- .../editor/common/controller/cursorCommon.ts | 66 +++++------ .../common/controller/cursorTypeOperations.ts | 2 +- .../modes/languageConfigurationRegistry.ts | 111 ++---------------- .../services/editorWorkerServiceImpl.ts | 29 +++-- src/vs/editor/common/services/webWorker.ts | 9 +- .../editor/common/viewModel/viewModelImpl.ts | 14 ++- .../contrib/suggest/test/wordDistance.test.ts | 3 +- .../browser/standaloneCodeEditor.ts | 9 +- .../standalone/browser/standaloneEditor.ts | 4 +- .../standalone/browser/standaloneServices.ts | 2 +- .../test/common/viewModel/testViewModel.ts | 3 +- .../api/browser/mainThreadLanguageFeatures.ts | 25 ++-- .../comments/browser/simpleCommentEditor.ts | 6 +- .../output/common/outputLinkProvider.ts | 6 +- .../languageDetectionWorkerServiceImpl.ts | 11 +- .../electron-sandbox/textMateService.ts | 4 +- 18 files changed, 131 insertions(+), 186 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index e91453cfb6578..b2420ba656255 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -56,6 +56,7 @@ import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBrea import { WordOperations } from 'vs/editor/common/controller/cursorWordOperations'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; let EDITOR_ID = 0; @@ -256,7 +257,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @INotificationService notificationService: INotificationService, - @IAccessibilityService accessibilityService: IAccessibilityService + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService, ) { super(); @@ -1525,7 +1527,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE model, DOMLineBreaksComputerFactory.create(), MonospaceLineBreaksComputerFactory.create(this._configuration.options), - (callback) => dom.scheduleAtNextAnimationFrame(callback) + (callback) => dom.scheduleAtNextAnimationFrame(callback), + this.languageConfigurationService ); listenersToRemove.push(model.onDidChangeDecorations((e) => this._onDidChangeModelDecorations.fire(e))); diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 795b87e0fee11..93530d2b67b9e 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -19,6 +19,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @@ -35,9 +36,10 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @INotificationService notificationService: INotificationService, - @IAccessibilityService accessibilityService: IAccessibilityService + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, ) { - super(domElement, { ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() }, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); + super(domElement, { ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() }, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index db02db9c4bc0b..88e042ffe5904 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -3,16 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { onUnexpectedError } from 'vs/base/common/errors'; import { ConfigurationChangedEvent, EditorAutoClosingEditStrategy, EditorAutoClosingStrategy, EditorAutoIndentStrategy, EditorAutoSurroundStrategy, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { ICommand, IConfiguration } from 'vs/editor/common/editorCommon'; import { ITextModel, PositionAffinity, TextModelResolvedOptions } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { AutoClosingPairs, IAutoClosingPair } from 'vs/editor/common/modes/languageConfiguration'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { AutoClosingPairs } from 'vs/editor/common/modes/languageConfiguration'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { createScopedLineTokens } from 'vs/editor/common/modes/supports'; +import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter'; import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; export { CursorColumns } from './cursorColumns'; @@ -104,7 +106,8 @@ export class CursorConfiguration { constructor( languageId: string, modelOptions: TextModelResolvedOptions, - configuration: IConfiguration + configuration: IConfiguration, + private readonly languageConfigurationService: ILanguageConfigurationService ) { this._languageId = languageId; @@ -135,13 +138,13 @@ export class CursorConfiguration { this._electricChars = null; this.shouldAutoCloseBefore = { - quote: CursorConfiguration._getShouldAutoClose(languageId, this.autoClosingQuotes), - bracket: CursorConfiguration._getShouldAutoClose(languageId, this.autoClosingBrackets) + quote: this._getShouldAutoClose(languageId, this.autoClosingQuotes), + bracket: this._getShouldAutoClose(languageId, this.autoClosingBrackets) }; - this.autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageId); + this.autoClosingPairs = this.languageConfigurationService.getLanguageConfiguration(languageId).getAutoClosingPairs(); - let surroundingPairs = CursorConfiguration._getSurroundingPairs(languageId); + let surroundingPairs = this.languageConfigurationService.getLanguageConfiguration(languageId).getSurroundingPairs(); if (surroundingPairs) { for (const pair of surroundingPairs) { this.surroundingPairs[pair.open] = pair.close; @@ -152,7 +155,7 @@ export class CursorConfiguration { public get electricChars() { if (!this._electricChars) { this._electricChars = {}; - let electricChars = CursorConfiguration._getElectricCharacters(this._languageId); + const electricChars = this.languageConfigurationService.getLanguageConfiguration(this._languageId).electricCharacter?.getElectricCharacters(); if (electricChars) { for (const char of electricChars) { this._electricChars[char] = true; @@ -162,25 +165,28 @@ export class CursorConfiguration { return this._electricChars; } - public normalizeIndentation(str: string): string { - return TextModel.normalizeIndentation(str, this.indentSize, this.insertSpaces); - } - - private static _getElectricCharacters(languageId: string): string[] | null { - try { - return LanguageConfigurationRegistry.getElectricCharacters(languageId); - } catch (e) { - onUnexpectedError(e); + /** + * Should return opening bracket type to match indentation with + */ + public onElectricCharacter(character: string, context: LineTokens, column: number): IElectricAction | null { + let scopedLineTokens = createScopedLineTokens(context, column - 1); + let electricCharacterSupport = this.languageConfigurationService.getLanguageConfiguration(scopedLineTokens.languageId).electricCharacter; + if (!electricCharacterSupport) { return null; } + return electricCharacterSupport.onElectricCharacter(character, scopedLineTokens, column - scopedLineTokens.firstCharOffset); } - private static _getShouldAutoClose(languageId: string, autoCloseConfig: EditorAutoClosingStrategy): (ch: string) => boolean { + public normalizeIndentation(str: string): string { + return TextModel.normalizeIndentation(str, this.indentSize, this.insertSpaces); + } + + private _getShouldAutoClose(languageId: string, autoCloseConfig: EditorAutoClosingStrategy): (ch: string) => boolean { switch (autoCloseConfig) { case 'beforeWhitespace': return autoCloseBeforeWhitespace; case 'languageDefined': - return CursorConfiguration._getLanguageDefinedShouldAutoClose(languageId); + return this._getLanguageDefinedShouldAutoClose(languageId); case 'always': return autoCloseAlways; case 'never': @@ -188,23 +194,9 @@ export class CursorConfiguration { } } - private static _getLanguageDefinedShouldAutoClose(languageId: string): (ch: string) => boolean { - try { - const autoCloseBeforeSet = LanguageConfigurationRegistry.getAutoCloseBeforeSet(languageId); - return c => autoCloseBeforeSet.indexOf(c) !== -1; - } catch (e) { - onUnexpectedError(e); - return autoCloseNever; - } - } - - private static _getSurroundingPairs(languageId: string): IAutoClosingPair[] | null { - try { - return LanguageConfigurationRegistry.getSurroundingPairs(languageId); - } catch (e) { - onUnexpectedError(e); - return null; - } + private _getLanguageDefinedShouldAutoClose(languageId: string): (ch: string) => boolean { + const autoCloseBeforeSet = this.languageConfigurationService.getLanguageConfiguration(languageId).getAutoCloseBeforeSet(); + return c => autoCloseBeforeSet.indexOf(c) !== -1; } } diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 4f8a560a404fa..9346d7b7092ff 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -773,7 +773,7 @@ export class TypeOperations { let electricAction: IElectricAction | null; try { - electricAction = LanguageConfigurationRegistry.onElectricCharacter(ch, lineTokens, position.column); + electricAction = config.onElectricCharacter(ch, lineTokens, position.column); } catch (e) { onUnexpectedError(e); return null; diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index 735247bdcdbd5..6e5d3549e918e 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -13,7 +13,7 @@ import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, CompleteEnterAction, AutoClosingPairs, CharacterPair, ExplicitLanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; import { createScopedLineTokens, ScopedLineTokens } from 'vs/editor/common/modes/supports'; import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair'; -import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter'; +import { BracketElectricCharacterSupport } from 'vs/editor/common/modes/supports/electricCharacter'; import { IndentConsts, IndentRulesSupport } from 'vs/editor/common/modes/supports/indentRules'; import { OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter'; import { RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets'; @@ -204,38 +204,6 @@ export class LanguageConfigurationRegistryImpl { return entries?.getResolvedConfiguration() || null; } - // begin electricCharacter - - private _getElectricCharacterSupport(languageId: string): BracketElectricCharacterSupport | null { - let value = this.getLanguageConfiguration(languageId); - if (!value) { - return null; - } - return value.electricCharacter || null; - } - - public getElectricCharacters(languageId: string): string[] { - let electricCharacterSupport = this._getElectricCharacterSupport(languageId); - if (!electricCharacterSupport) { - return []; - } - return electricCharacterSupport.getElectricCharacters(); - } - - /** - * Should return opening bracket type to match indentation with - */ - public onElectricCharacter(character: string, context: LineTokens, column: number): IElectricAction | null { - let scopedLineTokens = createScopedLineTokens(context, column - 1); - let electricCharacterSupport = this._getElectricCharacterSupport(scopedLineTokens.languageId); - if (!electricCharacterSupport) { - return null; - } - return electricCharacterSupport.onElectricCharacter(character, scopedLineTokens, column - scopedLineTokens.firstCharOffset); - } - - // end electricCharacter - public getComments(languageId: string): ICommentsConfiguration | null { let value = this.getLanguageConfiguration(languageId); if (!value) { @@ -244,66 +212,6 @@ export class LanguageConfigurationRegistryImpl { return value.comments || null; } - // begin characterPair - - private _getCharacterPairSupport(languageId: string): CharacterPairSupport | null { - let value = this.getLanguageConfiguration(languageId); - if (!value) { - return null; - } - return value.characterPair || null; - } - - public getAutoClosingPairs(languageId: string): AutoClosingPairs { - const characterPairSupport = this._getCharacterPairSupport(languageId); - return new AutoClosingPairs(characterPairSupport ? characterPairSupport.getAutoClosingPairs() : []); - } - - public getAutoCloseBeforeSet(languageId: string): string { - let characterPairSupport = this._getCharacterPairSupport(languageId); - if (!characterPairSupport) { - return CharacterPairSupport.DEFAULT_AUTOCLOSE_BEFORE_LANGUAGE_DEFINED; - } - return characterPairSupport.getAutoCloseBeforeSet(); - } - - public getSurroundingPairs(languageId: string): IAutoClosingPair[] { - let characterPairSupport = this._getCharacterPairSupport(languageId); - if (!characterPairSupport) { - return []; - } - return characterPairSupport.getSurroundingPairs(); - } - - // end characterPair - - public getWordDefinition(languageId: string): RegExp { - let value = this.getLanguageConfiguration(languageId); - if (!value) { - return ensureValidWordDefinition(null); - } - return ensureValidWordDefinition(value.wordDefinition || null); - } - - public getWordDefinitions(): [string, RegExp][] { - let result: [string, RegExp][] = []; - for (const [language, entries] of this._entries) { - const value = entries.getResolvedConfiguration(); - if (value) { - result.push([language, value.wordDefinition]); - } - } - return result; - } - - public getFoldingRules(languageId: string): FoldingRules { - let value = this.getLanguageConfiguration(languageId); - if (!value) { - return {}; - } - return value.foldingRules; - } - // begin Indent Rules public getIndentRulesSupport(languageId: string): IndentRulesSupport | null { @@ -967,11 +875,6 @@ export class ResolvedLanguageConfiguration { return this._electricCharacter; } - public getAutoClosingPairs(): AutoClosingPairs { - const characterPairSupport = this.characterPair; - return new AutoClosingPairs(characterPairSupport ? characterPairSupport.getAutoClosingPairs() : []); - } - public onEnter( autoIndent: EditorAutoIndentStrategy, previousLineText: string, @@ -989,6 +892,18 @@ export class ResolvedLanguageConfiguration { ); } + public getAutoClosingPairs(): AutoClosingPairs { + return new AutoClosingPairs(this.characterPair.getAutoClosingPairs()); + } + + public getAutoCloseBeforeSet(): string { + return this.characterPair.getAutoCloseBeforeSet(); + } + + public getSurroundingPairs(): IAutoClosingPair[] { + return this.characterPair.getSurroundingPairs(); + } + private static _handleComments( conf: LanguageConfiguration ): ICommentsConfiguration | null { diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index f5210b5440c64..03e92538c4575 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -13,7 +13,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { IChange } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -57,11 +57,12 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker constructor( @IModelService modelService: IModelService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService ) { super(); this._modelService = modelService; - this._workerManager = this._register(new WorkerManager(this._modelService)); + this._workerManager = this._register(new WorkerManager(this._modelService, languageConfigurationService)); this._logService = logService; // register default link-provider and default completions-provider @@ -75,7 +76,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker }); } })); - this._register(modes.CompletionProviderRegistry.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService))); + this._register(modes.CompletionProviderRegistry.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, languageConfigurationService))); } public override dispose(): void { @@ -145,7 +146,8 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider { constructor( workerManager: WorkerManager, configurationService: ITextResourceConfigurationService, - modelService: IModelService + modelService: IModelService, + private readonly languageConfigurationService: ILanguageConfigurationService ) { this._workerManager = workerManager; this._configurationService = configurationService; @@ -187,7 +189,7 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider { return undefined; // File too large, no other files } - const wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageId()); + const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); const word = model.getWordAtPosition(position); const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); const insert = replace.setEndPosition(position.lineNumber, position.column); @@ -218,7 +220,7 @@ class WorkerManager extends Disposable { private _editorWorkerClient: EditorWorkerClient | null; private _lastWorkerUsedTime: number; - constructor(modelService: IModelService) { + constructor(modelService: IModelService, private readonly languageConfigurationService: ILanguageConfigurationService) { super(); this._modelService = modelService; this._editorWorkerClient = null; @@ -272,7 +274,7 @@ class WorkerManager extends Disposable { public withWorker(): Promise { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this._modelService, false, 'editorWorkerService'); + this._editorWorkerClient = new EditorWorkerClient(this._modelService, false, 'editorWorkerService', this.languageConfigurationService); } return Promise.resolve(this._editorWorkerClient); } @@ -420,7 +422,12 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien private _modelManager: EditorModelManager | null; private _disposed = false; - constructor(modelService: IModelService, keepIdleModels: boolean, label: string | undefined) { + constructor( + modelService: IModelService, + keepIdleModels: boolean, + label: string | undefined, + private readonly languageConfigurationService: ILanguageConfigurationService + ) { super(); this._modelService = modelService; this._keepIdleModels = keepIdleModels; @@ -518,7 +525,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien if (!model) { return Promise.resolve(null); } - let wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageId()); + const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); let wordDef = wordDefRegExp.source; let wordDefFlags = regExpFlags(wordDefRegExp); return proxy.computeWordRanges(resource.toString(), range, wordDef, wordDefFlags); @@ -531,7 +538,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien if (!model) { return null; } - let wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageId()); + const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); let wordDef = wordDefRegExp.source; let wordDefFlags = regExpFlags(wordDefRegExp); return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags); diff --git a/src/vs/editor/common/services/webWorker.ts b/src/vs/editor/common/services/webWorker.ts index 2e3e7e67148e0..19d52cb560af0 100644 --- a/src/vs/editor/common/services/webWorker.ts +++ b/src/vs/editor/common/services/webWorker.ts @@ -7,13 +7,14 @@ import { URI } from 'vs/base/common/uri'; import { EditorWorkerClient } from 'vs/editor/common/services/editorWorkerServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import * as types from 'vs/base/common/types'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; /** * Create a new web worker that has model syncing capabilities built in. * Specify an AMD module to load that will `create` an object that will be proxied. */ -export function createWebWorker(modelService: IModelService, opts: IWebWorkerOptions): MonacoWebWorker { - return new MonacoWebWorkerImpl(modelService, opts); +export function createWebWorker(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker { + return new MonacoWebWorkerImpl(modelService, languageConfigurationService, opts); } /** @@ -67,8 +68,8 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWork private _foreignModuleCreateData: any | null; private _foreignProxy: Promise | null; - constructor(modelService: IModelService, opts: IWebWorkerOptions) { - super(modelService, opts.keepIdleModels || false, opts.label); + constructor(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions) { + super(modelService, opts.keepIdleModels || false, opts.label, languageConfigurationService); this._foreignModuleId = opts.moduleId; this._foreignModuleCreateData = opts.createData || null; this._foreignModuleHost = opts.host || null; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 191cbab4d4319..62b2ecc972fd0 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -23,6 +23,7 @@ import { IActiveIndentGuideInfo, BracketGuideOptions, IndentGuide } from 'vs/edi import { ModelDecorationMinimapOptions, ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; @@ -65,7 +66,8 @@ export class ViewModel extends Disposable implements IViewModel { model: ITextModel, domLineBreaksComputerFactory: ILineBreaksComputerFactory, monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory, - scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable + scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable, + private readonly languageConfigurationService: ILanguageConfigurationService ) { super(); @@ -74,7 +76,7 @@ export class ViewModel extends Disposable implements IViewModel { this.model = model; this._eventDispatcher = new ViewModelEventDispatcher(); this.onEvent = this._eventDispatcher.onEvent; - this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50)); this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); this._hasFocus = false; @@ -256,7 +258,7 @@ export class ViewModel extends Disposable implements IViewModel { } if (CursorConfiguration.shouldRecreate(e)) { - this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._cursor.updateConfiguration(this.cursorConfig); } } @@ -413,12 +415,12 @@ export class ViewModel extends Disposable implements IViewModel { this._register(this.model.onDidChangeLanguageConfiguration((e) => { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent()); - this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._cursor.updateConfiguration(this.cursorConfig); })); this._register(this.model.onDidChangeLanguage((e) => { - this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._cursor.updateConfiguration(this.cursorConfig); })); @@ -439,7 +441,7 @@ export class ViewModel extends Disposable implements IViewModel { this._updateConfigurationViewLineCount.schedule(); } - this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._cursor.updateConfiguration(this.cursorConfig); })); diff --git a/src/vs/editor/contrib/suggest/test/wordDistance.test.ts b/src/vs/editor/contrib/suggest/test/wordDistance.test.ts index 251583b3a48a4..40964b2b8be9a 100644 --- a/src/vs/editor/contrib/suggest/test/wordDistance.test.ts +++ b/src/vs/editor/contrib/suggest/test/wordDistance.test.ts @@ -22,6 +22,7 @@ import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { NullLogService } from 'vs/platform/log/common/log'; suite('suggest, word distance', function () { @@ -65,7 +66,7 @@ suite('suggest, word distance', function () { private _worker = new EditorSimpleWorker(new class extends mock() { }, null); constructor() { - super(modelService, new class extends mock() { }, new NullLogService()); + super(modelService, new class extends mock() { }, new NullLogService(), new TestLanguageConfigurationService()); this._worker.acceptNewModel({ url: model.uri.toString(), lines: model.getLinesContent(), diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 7d806b8cb52fc..61c566e207c18 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -36,6 +36,7 @@ import { ILanguageSelection, ILanguageService } from 'vs/editor/common/services/ import { URI } from 'vs/base/common/uri'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; /** * Description of an action contribution @@ -270,12 +271,13 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon @IKeybindingService keybindingService: IKeybindingService, @IThemeService themeService: IThemeService, @INotificationService notificationService: INotificationService, - @IAccessibilityService accessibilityService: IAccessibilityService + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, ) { const options = { ..._options }; options.ariaLabel = options.ariaLabel || StandaloneCodeEditorNLS.editorViewAccessibleLabel; options.ariaLabel = options.ariaLabel + ';' + (StandaloneCodeEditorNLS.accessibilityHelpMessage); - super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); + super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService); if (keybindingService instanceof StandaloneKeybindingService) { this._standaloneKeybindingService = keybindingService; @@ -415,6 +417,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon @IAccessibilityService accessibilityService: IAccessibilityService, @IModelService modelService: IModelService, @ILanguageService languageService: ILanguageService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, ) { const options = { ..._options }; updateConfigurationService(configurationService, options, false); @@ -427,7 +430,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon } let _model: ITextModel | null | undefined = options.model; delete options.model; - super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, keybindingService, themeService, notificationService, accessibilityService); + super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, keybindingService, themeService, notificationService, accessibilityService, languageConfigurationService); this._contextViewService = contextViewService; this._configurationService = configurationService; diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 45049b5739ed8..d854758e30300 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -43,6 +43,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { splitLines } from 'vs/base/common/strings'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; type Omit = Pick>; @@ -91,6 +92,7 @@ export function create(domElement: HTMLElement, options?: IStandaloneEditorConst services.get(IAccessibilityService), services.get(IModelService), services.get(ILanguageService), + services.get(ILanguageConfigurationService), ); }); } @@ -240,7 +242,7 @@ export function onDidChangeModelLanguage(listener: (e: { readonly model: ITextMo * Specify an AMD module to load that will `create` an object that will be proxied. */ export function createWebWorker(opts: IWebWorkerOptions): MonacoWebWorker { - return actualCreateWebWorker(StaticServices.modelService.get(), opts); + return actualCreateWebWorker(StaticServices.modelService.get(), StaticServices.languageConfigurationService.get(), opts); } /** diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index e449c4441d3fd..da61da702be24 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -184,7 +184,7 @@ export module StaticServices { export const storageService = define(IStorageService, () => new InMemoryStorageService()); - export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o), logService.get(o))); + export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o), logService.get(o), languageConfigurationService.get(o))); } export class DynamicStandaloneServices extends Disposable { diff --git a/src/vs/editor/test/common/viewModel/testViewModel.ts b/src/vs/editor/test/common/viewModel/testViewModel.ts index 38f1fc586f165..b1871d9872e1a 100644 --- a/src/vs/editor/test/common/viewModel/testViewModel.ts +++ b/src/vs/editor/test/common/viewModel/testViewModel.ts @@ -9,6 +9,7 @@ import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void { const EDITOR_ID = 1; @@ -16,7 +17,7 @@ export function testViewModel(text: string[], options: IEditorOptions, callback: const configuration = new TestConfiguration(options); const model = createTextModel(text.join('\n')); const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(configuration.options); - const viewModel = new ViewModel(EDITOR_ID, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); + const viewModel = new ViewModel(EDITOR_ID, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!, new TestLanguageConfigurationService()); callback(viewModel, model); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index bd230ce2136d6..d7920db12455b 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -12,7 +12,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto } from '../common/extHost.protocol'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { ILanguageConfigurationService, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { ILanguageService } from 'vs/editor/common/services/languageService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -34,15 +34,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha constructor( extHostContext: IExtHostContext, @ILanguageService languageService: ILanguageService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures); this._languageService = languageService; if (this._languageService) { const updateAllWordDefinitions = () => { - const langWordPairs = LanguageConfigurationRegistry.getWordDefinitions(); let wordDefinitionDtos: ILanguageWordDefinitionDto[] = []; - for (const [languageId, wordDefinition] of langWordPairs) { + for (const languageId of languageService.getRegisteredLanguageIds()) { + const wordDefinition = languageConfigurationService.getLanguageConfiguration(languageId).getWordDefinition(); wordDefinitionDtos.push({ languageId: languageId, regexSource: wordDefinition.source, @@ -51,13 +52,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } this._proxy.$setWordDefinitions(wordDefinitionDtos); }; - LanguageConfigurationRegistry.onDidChange((e) => { - const wordDefinition = LanguageConfigurationRegistry.getWordDefinition(e.languageId); - this._proxy.$setWordDefinitions([{ - languageId: e.languageId, - regexSource: wordDefinition.source, - regexFlags: wordDefinition.flags - }]); + languageConfigurationService.onDidChange((e) => { + if (!e.languageId) { + updateAllWordDefinitions(); + } else { + const wordDefinition = languageConfigurationService.getLanguageConfiguration(e.languageId).getWordDefinition(); + this._proxy.$setWordDefinitions([{ + languageId: e.languageId, + regexSource: wordDefinition.source, + regexFlags: wordDefinition.flags + }]); + } }); updateAllWordDefinitions(); } diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index cbc949091249c..e851ce40cefd0 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -23,6 +23,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; export const ctxCommentEditorFocused = new RawContextKey('commentEditorFocused', false); @@ -44,7 +45,8 @@ export class SimpleCommentEditor extends CodeEditorWidget { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @INotificationService notificationService: INotificationService, - @IAccessibilityService accessibilityService: IAccessibilityService + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService ) { const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { isSimpleWidget: true, @@ -57,7 +59,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { ] }; - super(domElement, options, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); + super(domElement, options, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService); this._commentEditorFocused = ctxCommentEditorFocused.bindTo(contextKeyService); this._commentEditorEmpty = CommentContextKeys.commentIsEmpty.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/output/common/outputLinkProvider.ts b/src/vs/workbench/contrib/output/common/outputLinkProvider.ts index 7e03f0907f664..cd30d54678a05 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkProvider.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkProvider.ts @@ -12,6 +12,7 @@ import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/contrib/output/common/ import { MonacoWebWorker, createWebWorker } from 'vs/editor/common/services/webWorker'; import { ICreateData, OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; export class OutputLinkProvider { @@ -23,7 +24,8 @@ export class OutputLinkProvider { constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IModelService private readonly modelService: IModelService + @IModelService private readonly modelService: IModelService, + @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService ) { this.disposeWorkerScheduler = new RunOnceScheduler(() => this.disposeWorker(), OutputLinkProvider.DISPOSE_WORKER_TIME); @@ -67,7 +69,7 @@ export class OutputLinkProvider { workspaceFolders: this.contextService.getWorkspace().folders.map(folder => folder.uri.toString()) }; - this.worker = createWebWorker(this.modelService, { + this.worker = createWebWorker(this.modelService, this.languageConfigurationService, { moduleId: 'vs/workbench/contrib/output/common/outputLinkComputer', createData, label: 'outputLinkComputer' diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index 811f5654182b1..84709216b1363 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -17,6 +17,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EditorWorkerClient, EditorWorkerHost } from 'vs/editor/common/services/editorWorkerServiceImpl'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; const moduleLocation = '../../../../../../node_modules/@vscode/vscode-languagedetection'; const moduleLocationAsar = '../../../../../../node_modules.asar/@vscode/vscode-languagedetection'; @@ -33,6 +34,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet @IConfigurationService private readonly _configurationService: IConfigurationService, @IModelService modelService: IModelService, @ITelemetryService telemetryService: ITelemetryService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService ) { super(); @@ -48,7 +50,9 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet : FileAccess.asBrowserUri(`${moduleLocation}/model/model.json`, require).toString(true), this._environmentService.isBuilt && !isWeb ? FileAccess.asBrowserUri(`${moduleLocationAsar}/model/group1-shard1of1.bin`, require).toString(true) - : FileAccess.asBrowserUri(`${moduleLocation}/model/group1-shard1of1.bin`, require).toString(true)); + : FileAccess.asBrowserUri(`${moduleLocation}/model/group1-shard1of1.bin`, require).toString(true), + languageConfigurationService + ); } public isEnabledForMode(languageId: string): boolean { @@ -121,9 +125,10 @@ export class LanguageDetectionWorkerClient extends EditorWorkerClient { private readonly _telemetryService: ITelemetryService, private readonly _indexJsUri: string, private readonly _modelJsonUri: string, - private readonly _weightsUri: string + private readonly _weightsUri: string, + languageConfigurationService: ILanguageConfigurationService, ) { - super(modelService, true, 'languageDetectionWorkerService'); + super(modelService, true, 'languageDetectionWorkerService', languageConfigurationService); } private _getOrCreateLanguageDetectionWorker(): Promise> { diff --git a/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts b/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts index ca2d95ceb9e2f..d13b198109dcf 100644 --- a/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts @@ -27,6 +27,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IProgressService } from 'vs/platform/progress/common/progress'; import { FileAccess } from 'vs/base/common/network'; import { ILanguageIdCodec } from 'vs/editor/common/modes'; +import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; const RUN_TEXTMATE_IN_WORKER = false; @@ -158,6 +159,7 @@ export class TextMateService extends AbstractTextMateService { @IProgressService progressService: IProgressService, @IModelService private readonly _modelService: IModelService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, ) { super(languageService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, progressService); this._worker = null; @@ -200,7 +202,7 @@ export class TextMateService extends AbstractTextMateService { if (RUN_TEXTMATE_IN_WORKER) { const workerHost = new TextMateWorkerHost(this, this._extensionResourceLoaderService); - const worker = createWebWorker(this._modelService, { + const worker = createWebWorker(this._modelService, this._languageConfigurationService, { createData: { grammarDefinitions }, From 95d88db1003feade0f29a2e0b1f871a0f0d3e866 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Dec 2021 11:55:56 +0100 Subject: [PATCH 0469/2210] improve comments around codicon and icon themes --- src/vs/base/browser/ui/codicons/codicon/codicon.css | 2 +- src/vs/base/common/codicons.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.css b/src/vs/base/browser/ui/codicons/codicon/codicon.css index 77cc04a1df402..4b256e0917c28 100644 --- a/src/vs/base/browser/ui/codicons/codicon/codicon.css +++ b/src/vs/base/browser/ui/codicons/codicon/codicon.css @@ -22,4 +22,4 @@ -ms-user-select: none; } -/* icon rules are dynamically created in codiconStyles */ +/* icon rules are dynamically created by the platform theme service (see iconsStyleSheet.ts) */ diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index b4b1b2d959c02..915974420347d 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -25,8 +25,8 @@ export function getCodiconAriaLabel(text: string | undefined) { * The Codicon library is a set of default icons that are built-in in VS Code. * * In the product (outside of base) Codicons should only be used as defaults. In order to have all icons in VS Code - * themeable, component should ise define new, component specific icons using `iconRegistry.registerIcon`. - * In that call a Codicon can be names as default. + * themeable, component should define new, UI component specific icons using `iconRegistry.registerIcon`. + * In that call a Codicon can be named as default. */ export class Codicon implements CSSIcon { @@ -42,7 +42,7 @@ export class Codicon implements CSSIcon { private static _allCodicons : Codicon[] = []; /** - * @returns Returns all Codicons. Only to be used by the icon registry in platform. + * @returns Returns all default icons covered by the codicon font. Only to be used by the icon registry in platform. */ public static getAll() : readonly Codicon[] { return Codicon._allCodicons; From 55819ee181e1bc90a8330d1ca5ef3a194274ca01 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 9 Dec 2021 11:56:53 +0100 Subject: [PATCH 0470/2210] be more strict when suggesting snippets at word ends, https://github.com/microsoft/vscode/issues/138707 --- .../browser/snippetCompletionProvider.ts | 7 +++ .../test/browser/snippetsService.test.ts | 53 +++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index e9b46b6e930b9..74103ead7b348 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -71,6 +71,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider { const snippets = new Set(await this._snippets.getSnippets(languageId)); const lineContentLow = model.getLineContent(position.lineNumber).toLowerCase(); + const wordUntil = model.getWordUntilPosition(position).word.toLowerCase(); const suggestions: SnippetCompletion[] = []; const columnOffset = position.column - 1; @@ -79,6 +80,12 @@ export class SnippetCompletionProvider implements CompletionItemProvider { for (const snippet of snippets) { + if (wordUntil && snippet.prefixLow.length < wordUntil.length && !isPatternInWord(snippet.prefixLow, 0, snippet.prefixLow.length, wordUntil, 0, wordUntil.length)) { + // when at a word the snippet prefix must match + continue; + } + + for (let pos = Math.max(0, columnOffset - snippet.prefixLow.length); pos < lineContentLow.length; pos++) { if (context.triggerKind === CompletionTriggerKind.TriggerCharacter && !snippet.prefixLow.startsWith(triggerCharacterLow)) { diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index f0982a2e5ca44..a5ce99557d149 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider'; +import { SnippetCompletion, SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider'; import { Position } from 'vs/editor/common/core/position'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { LanguageService } from 'vs/editor/common/services/languageServiceImpl'; @@ -92,12 +92,17 @@ suite('SnippetsService', function () { }); }); - test('snippet completions - simple 2', function () { + test('snippet completions - simple 2', async function () { const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); const model = disposables.add(createTextModel('hello ', undefined, 'fooLang')); - return provider.provideCompletionItems(model, new Position(1, 6), context)!.then(result => { + await provider.provideCompletionItems(model, new Position(1, 6) /* hello| */, context)!.then(result => { + assert.strictEqual(result.incomplete, undefined); + assert.strictEqual(result.suggestions.length, 0); + }); + + await provider.provideCompletionItems(model, new Position(1, 7) /* hello |*/, context)!.then(result => { assert.strictEqual(result.incomplete, undefined); assert.strictEqual(result.suggestions.length, 2); }); @@ -641,4 +646,46 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); model.dispose(); }); + + test('Snippet suggestions are too eager #138707 (word)', async function () { + snippetService = new SimpleSnippetService([ + new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User), + new Snippet(['fooLang'], 't', 't', '', 'value', '', SnippetSource.User), + new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User), + ]); + + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + let model = createTextModel('\'hellot\'', undefined, 'fooLang'); + + let result = await provider.provideCompletionItems( + model, + new Position(1, 8), + { triggerKind: CompletionTriggerKind.Invoke } + )!; + + assert.strictEqual(result.suggestions.length, 1); + assert.strictEqual((result.suggestions[0]).label.label, 't'); + model.dispose(); + }); + + test('Snippet suggestions are too eager #138707 (no word)', async function () { + snippetService = new SimpleSnippetService([ + new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User), + new Snippet(['fooLang'], 't', 't', '', 'value', '', SnippetSource.User), + new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User), + ]); + + const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + let model = createTextModel(')*&^', undefined, 'fooLang'); + + let result = await provider.provideCompletionItems( + model, + new Position(1, 5), + { triggerKind: CompletionTriggerKind.Invoke } + )!; + + assert.strictEqual(result.suggestions.length, 1); + assert.strictEqual((result.suggestions[0]).label.label, '^y'); + model.dispose(); + }); }); From aed73fd423b8925934e22b67d26166bcc7907f20 Mon Sep 17 00:00:00 2001 From: John Murray Date: Thu, 9 Dec 2021 11:22:44 +0000 Subject: [PATCH 0471/2210] Correct the description of `screencastMode.keyboardShortcutsFormat` setting (#138644) (#138645) --- src/vs/workbench/browser/actions/developerActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 8758fc91d8ea6..c5a04846dc070 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -353,7 +353,7 @@ configurationRegistry.registerConfiguration({ localize('keyboardShortcutsFormat.commandAndKeys', "Command title and keys."), localize('keyboardShortcutsFormat.commandWithGroupAndKeys', "Command title and keys, with the command prefixed by its group.") ], - description: localize('screencastMode.keyboardShortcutsFormat', "Controls what is displayed in the keyboard overlay when showing only shortcuts."), + description: localize('screencastMode.keyboardShortcutsFormat', "Controls what is displayed in the keyboard overlay when showing shortcuts."), default: 'commandAndKeys' }, 'screencastMode.onlyKeyboardShortcuts': { From c04c29d4963d6f5e23cd410f6ea48daabf071be2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Dec 2021 12:36:37 +0100 Subject: [PATCH 0472/2210] smoke - mention playwright trace website --- test/smoke/test/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/smoke/test/index.js b/test/smoke/test/index.js index 93d1a8a55cb18..913db7f893084 100644 --- a/test/smoke/test/index.js +++ b/test/smoke/test/index.js @@ -50,6 +50,8 @@ mocha.run(failures => { # Logs are attached as build artefact and can be downloaded # # from the build Summary page (Summary -> Related -> N published) # # # +# Show playwright traces on: https://trace.playwright.dev/ # +# # ################################################################### `); } else { From ee3e404b71ad5b6503b3aafe50f1c7518c8e4c2a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 9 Dec 2021 12:53:16 +0100 Subject: [PATCH 0473/2210] disable failing smoketest, https://github.com/microsoft/vscode/issues/138748 --- test/smoke/src/areas/search/search.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 83546ff24a82d..3156be0d0adaa 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -36,7 +36,7 @@ export function setup(logger: Logger) { await app.workbench.search.waitForResultText('16 results in 5 files'); }); - it('searches only for *.js files & checks for correct result number', async function () { + it.skip('searches only for *.js files & checks for correct result number', async function () { const app = this.app as Application; await app.workbench.search.searchFor('body'); await app.workbench.search.showQueryDetails(); From e65a5b98d96e99a3a205994328c2bb4e3cdebfe5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 9 Dec 2021 14:18:03 +0100 Subject: [PATCH 0474/2210] update notebooks --- .vscode/notebooks/api.github-issues | 2 +- .vscode/notebooks/inbox.github-issues | 2 +- .vscode/notebooks/my-work.github-issues | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 14d330b52cfb5..613a3dd3ce955 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"December 2021\"" + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"January 2022\"" }, { "kind": 1, diff --git a/.vscode/notebooks/inbox.github-issues b/.vscode/notebooks/inbox.github-issues index be6afc784c7e3..2fd0b34e66cee 100644 --- a/.vscode/notebooks/inbox.github-issues +++ b/.vscode/notebooks/inbox.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$inbox -label:\"needs more info\" sort:created-asc" + "value": "$inbox -label:\"needs more info\" sort:created-desc" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 5401e27d2d6e6..cae5d55f1a74c 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github\n\n// current milestone name\n$milestone=milestone:\"December 2021\"" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github\n\n// current milestone name\n$milestone=milestone:\"January 2022\"" }, { "kind": 1, From 9947a32a3ea0f2a49282a62c8f70d0b41d4fb1cb Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 9 Dec 2021 14:20:02 +0100 Subject: [PATCH 0475/2210] improve scm input history storage related to #138605 --- src/vs/base/common/history.ts | 7 +- .../contrib/scm/common/scmService.ts | 66 ++++++++++++++----- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 387b31426a2c1..2cbe76962ebfe 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -147,8 +147,13 @@ export class HistoryNavigator2 { } } - replaceLast(value: T): void { + /** + * @returns old last value + */ + replaceLast(value: T): T { + const oldValue = this.tail.value; this.tail.value = value; + return oldValue; } isAtEnd(): boolean { diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 1db24655ca5db..953bcd231ec0e 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -11,6 +11,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { HistoryNavigator2 } from 'vs/base/common/history'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Iterable } from 'vs/base/common/iterator'; class SCMInput implements ISCMInput { @@ -80,19 +81,21 @@ class SCMInput implements ISCMInput { readonly onDidChangeValidateInput: Event = this._onDidChangeValidateInput.event; private historyNavigator: HistoryNavigator2; + private didChangeHistory: boolean; - private static didCleanup = false; + private static didGarbageCollect = false; private static migrateAndGarbageCollectStorage(storageService: IStorageService): void { - if (SCMInput.didCleanup) { + if (SCMInput.didGarbageCollect) { return; } - const keys = storageService.keys(StorageScope.GLOBAL, StorageTarget.USER) - .filter(key => key.startsWith('scm/input:')); + // Migrate from old format // TODO@joao: remove this migration code a few releases + const userKeys = Iterable.filter(Iterable.from(storageService.keys(StorageScope.GLOBAL, StorageTarget.USER)), key => key.startsWith('scm/input:')); - for (const key of keys) { + for (const key of userKeys) { try { - const history = JSON.parse(storageService.get(key, StorageScope.GLOBAL, '[]')); + const rawHistory = storageService.get(key, StorageScope.GLOBAL, ''); + const history = JSON.parse(rawHistory); if (Array.isArray(history)) { if (history.length === 0 || (history.length === 1 && history[0] === '')) { @@ -100,9 +103,26 @@ class SCMInput implements ISCMInput { storageService.remove(key, StorageScope.GLOBAL); } else { // migrate existing histories to have a timestamp - storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.GLOBAL, StorageTarget.USER); + storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.GLOBAL, StorageTarget.MACHINE); } - } else if (Array.isArray(history?.history) && Number.isInteger(history?.timestamp) && new Date().getTime() - history?.timestamp > 2592000000) { + } else { + // move to MACHINE target + storageService.store(key, rawHistory, StorageScope.GLOBAL, StorageTarget.MACHINE); + } + } catch { + // remove unparseable entries + storageService.remove(key, StorageScope.GLOBAL); + } + } + + // Garbage collect + const machineKeys = Iterable.filter(Iterable.from(storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE)), key => key.startsWith('scm/input:')); + + for (const key of machineKeys) { + try { + const history = JSON.parse(storageService.get(key, StorageScope.GLOBAL, '')); + + if (Array.isArray(history?.history) && Number.isInteger(history?.timestamp) && new Date().getTime() - history?.timestamp > 2592000000) { // garbage collect after 30 days storageService.remove(key, StorageScope.GLOBAL); } @@ -112,7 +132,7 @@ class SCMInput implements ISCMInput { } } - SCMInput.didCleanup = true; + SCMInput.didGarbageCollect = true; } constructor( @@ -139,18 +159,26 @@ class SCMInput implements ISCMInput { } this.historyNavigator = new HistoryNavigator2(history, 50); + this.didChangeHistory = false; if (key) { this.storageService.onWillSaveState(_ => { if (this.historyNavigator.isAtEnd()) { - this.historyNavigator.replaceLast(this._value); + this.saveValue(); + } + + if (!this.didChangeHistory) { + return; } const history = [...this.historyNavigator]; - if (history.length > 1 || (history.length === 1 && history[0] !== '')) { - storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.GLOBAL, StorageTarget.USER); + if (history.length === 0 || (history.length === 1 && history[0] === '')) { + return; } + + storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.GLOBAL, StorageTarget.MACHINE); + this.didChangeHistory = false; }); } } @@ -161,8 +189,9 @@ class SCMInput implements ISCMInput { } if (!transient) { - this.historyNavigator.replaceLast(this._value); + this.saveValue(); this.historyNavigator.add(value); + this.didChangeHistory = true; } this._value = value; @@ -173,7 +202,7 @@ class SCMInput implements ISCMInput { if (this.historyNavigator.isAtEnd()) { return; } else if (!this.historyNavigator.has(this.value)) { - this.historyNavigator.replaceLast(this._value); + this.saveValue(); this.historyNavigator.resetCursor(); } @@ -183,15 +212,20 @@ class SCMInput implements ISCMInput { showPreviousHistoryValue(): void { if (this.historyNavigator.isAtEnd()) { - this.historyNavigator.replaceLast(this._value); + this.saveValue(); } else if (!this.historyNavigator.has(this._value)) { - this.historyNavigator.replaceLast(this._value); + this.saveValue(); this.historyNavigator.resetCursor(); } const value = this.historyNavigator.previous(); this.setValue(value, true, SCMInputChangeReason.HistoryPrevious); } + + private saveValue(): void { + const oldValue = this.historyNavigator.replaceLast(this._value); + this.didChangeHistory = this.didChangeHistory || (oldValue !== this._value); + } } class SCMRepository implements ISCMRepository { From 5f75c4a41091ac36faea06bf86965d507ce77fa8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 9 Dec 2021 14:19:36 +0100 Subject: [PATCH 0476/2210] Fixes #136286. --- .../common/viewModel/modelLineProjection.ts | 131 ++++++++++-------- 1 file changed, 72 insertions(+), 59 deletions(-) diff --git a/src/vs/editor/common/viewModel/modelLineProjection.ts b/src/vs/editor/common/viewModel/modelLineProjection.ts index 390fb638bbb29..8657011fb93c8 100644 --- a/src/vs/editor/common/viewModel/modelLineProjection.ts +++ b/src/vs/editor/common/viewModel/modelLineProjection.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IViewLineTokens, LineTokens } from 'vs/editor/common/core/lineTokens'; +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 { EndOfLinePreference, ITextModel, PositionAffinity } from 'vs/editor/common/model'; @@ -91,14 +91,6 @@ class ModelLineProjection implements IModelLineProjection { return this._lineBreakData.getOutputLineCount(); } - private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number { - return this._lineBreakData.translateToInputOffset(outputLineIndex, 0); - } - - private getInputEndOffsetOfOutputLineIndex(outputLineIndex: number): number { - return this._lineBreakData.translateToInputOffset(outputLineIndex, this._lineBreakData.getMaxOutputOffset(outputLineIndex)); - } - public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string { this.assertVisible(); @@ -144,63 +136,96 @@ class ModelLineProjection implements IModelLineProjection { return this._lineBreakData.getMaxOutputOffset(outputLineIndex) + 1; } + /** + * Try using {@link getViewLinesData} instead. + */ public getViewLineData(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): ViewLineData { + const arr = new Array(); + this.getViewLinesData(model, modelLineNumber, outputLineIndex, outputLineIndex + 1, 0, [true], arr); + return arr[0]; + } + + public getViewLinesData(model: ISimpleModel, modelLineNumber: number, fromOutputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: Array): void { this.assertVisible(); + const lineBreakData = this._lineBreakData; - const deltaStartIndex = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0); const injectionOffsets = lineBreakData.injectionOffsets; const injectionOptions = lineBreakData.injectionOptions; - let tokens: IViewLineTokens; - let inlineDecorations: null | SingleLineInlineDecoration[]; + let inlineDecorationsPerOutputLine: SingleLineInlineDecoration[][] | null = null; + if (injectionOffsets) { - const lineTokens = model.getLineTokens(modelLineNumber).withInserted(injectionOffsets.map((offset, idx) => ({ - offset, - text: injectionOptions![idx].content, - tokenMetadata: LineTokens.defaultTokenMetadata - }))); + inlineDecorationsPerOutputLine = []; + let totalInjectedTextLengthBefore = 0; + let currentInjectedOffset = 0; - const lineStartOffsetInInputWithInjections = outputLineIndex > 0 ? lineBreakData.breakOffsets[outputLineIndex - 1] : 0; - const lineEndOffsetInInputWithInjections = lineBreakData.breakOffsets[outputLineIndex]; + for (let outputLineIndex = 0; outputLineIndex < lineBreakData.getOutputLineCount(); outputLineIndex++) { + const inlineDecorations = new Array(); + inlineDecorationsPerOutputLine[outputLineIndex] = inlineDecorations; - tokens = lineTokens.sliceAndInflate(lineStartOffsetInInputWithInjections, lineEndOffsetInInputWithInjections, deltaStartIndex); - inlineDecorations = new Array(); + const lineStartOffsetInInputWithInjections = outputLineIndex > 0 ? lineBreakData.breakOffsets[outputLineIndex - 1] : 0; + const lineEndOffsetInInputWithInjections = lineBreakData.breakOffsets[outputLineIndex]; - let totalInjectedTextLengthBefore = 0; - for (let i = 0; i < injectionOffsets.length; i++) { - const length = injectionOptions![i].content.length; - const injectedTextStartOffsetInInputWithInjections = injectionOffsets[i] + totalInjectedTextLengthBefore; - const injectedTextEndOffsetInInputWithInjections = injectionOffsets[i] + totalInjectedTextLengthBefore + length; - - if (injectedTextStartOffsetInInputWithInjections > lineEndOffsetInInputWithInjections) { - // Injected text only starts in later wrapped lines. - break; - } + while (currentInjectedOffset < injectionOffsets.length) { + const length = injectionOptions![currentInjectedOffset].content.length; + const injectedTextStartOffsetInInputWithInjections = injectionOffsets[currentInjectedOffset] + totalInjectedTextLengthBefore; + const injectedTextEndOffsetInInputWithInjections = injectedTextStartOffsetInInputWithInjections + length; - if (lineStartOffsetInInputWithInjections < injectedTextEndOffsetInInputWithInjections) { - // Injected text ends after or in this line (but also starts in or before this line). - const options = injectionOptions![i]; - if (options.inlineClassName) { - const offset = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0); - const start = offset + Math.max(injectedTextStartOffsetInInputWithInjections - lineStartOffsetInInputWithInjections, 0); - const end = offset + Math.min(injectedTextEndOffsetInInputWithInjections - lineStartOffsetInInputWithInjections, lineEndOffsetInInputWithInjections); - if (start !== end) { - inlineDecorations.push(new SingleLineInlineDecoration(start, end, options.inlineClassName, options.inlineClassNameAffectsLetterSpacing!)); + if (injectedTextStartOffsetInInputWithInjections > lineEndOffsetInInputWithInjections) { + // Injected text only starts in later wrapped lines. + break; + } + + if (lineStartOffsetInInputWithInjections < injectedTextEndOffsetInInputWithInjections) { + // Injected text ends after or in this line (but also starts in or before this line). + const options = injectionOptions![currentInjectedOffset]; + if (options.inlineClassName) { + const offset = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0); + const start = offset + Math.max(injectedTextStartOffsetInInputWithInjections - lineStartOffsetInInputWithInjections, 0); + const end = offset + Math.min(injectedTextEndOffsetInInputWithInjections - lineStartOffsetInInputWithInjections, lineEndOffsetInInputWithInjections); + if (start !== end) { + inlineDecorations.push(new SingleLineInlineDecoration(start, end, options.inlineClassName, options.inlineClassNameAffectsLetterSpacing!)); + } } } - } - totalInjectedTextLengthBefore += length; + totalInjectedTextLengthBefore += length; + currentInjectedOffset++; + } } + } + + let lineWithInjections: LineTokens; + if (injectionOffsets) { + lineWithInjections = model.getLineTokens(modelLineNumber).withInserted(injectionOffsets.map((offset, idx) => ({ + offset, + text: injectionOptions![idx].content, + tokenMetadata: LineTokens.defaultTokenMetadata + }))); } else { - const startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex); - const endOffset = this.getInputEndOffsetOfOutputLineIndex(outputLineIndex); - const lineTokens = model.getLineTokens(modelLineNumber); - tokens = lineTokens.sliceAndInflate(startOffset, endOffset, deltaStartIndex); - inlineDecorations = null; + lineWithInjections = model.getLineTokens(modelLineNumber); } + for (let outputLineIndex = fromOutputLineIndex; outputLineIndex < toOutputLineIndex; outputLineIndex++) { + let globalIndex = globalStartIndex + outputLineIndex - fromOutputLineIndex; + if (!needed[globalIndex]) { + result[globalIndex] = null; + continue; + } + result[globalIndex] = this._getViewLineData(lineWithInjections, inlineDecorationsPerOutputLine ? inlineDecorationsPerOutputLine[outputLineIndex] : null, outputLineIndex); + } + } + + private _getViewLineData(lineWithInjections: LineTokens, inlineDecorations: null | SingleLineInlineDecoration[], outputLineIndex: number): ViewLineData { + this.assertVisible(); + const lineBreakData = this._lineBreakData; + const deltaStartIndex = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0); + + const lineStartOffsetInInputWithInjections = outputLineIndex > 0 ? lineBreakData.breakOffsets[outputLineIndex - 1] : 0; + const lineEndOffsetInInputWithInjections = lineBreakData.breakOffsets[outputLineIndex]; + const tokens = lineWithInjections.sliceAndInflate(lineStartOffsetInInputWithInjections, lineEndOffsetInInputWithInjections, deltaStartIndex); + let lineContent = tokens.getLineContent(); if (outputLineIndex > 0) { lineContent = spaces(lineBreakData.wrappedTextIndentLength) + lineContent; @@ -222,18 +247,6 @@ class ModelLineProjection implements IModelLineProjection { ); } - public getViewLinesData(model: ITextModel, modelLineNumber: number, fromOutputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: Array): void { - this.assertVisible(); - for (let outputLineIndex = fromOutputLineIndex; outputLineIndex < toOutputLineIndex; outputLineIndex++) { - let globalIndex = globalStartIndex + outputLineIndex - fromOutputLineIndex; - if (!needed[globalIndex]) { - result[globalIndex] = null; - continue; - } - result[globalIndex] = this.getViewLineData(model, modelLineNumber, outputLineIndex); - } - } - public getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number { this.assertVisible(); return this._lineBreakData.translateToInputOffset(outputLineIndex, outputColumn - 1) + 1; From 134b54cfb5d418adac155a30ba3d72d344cb1721 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 9 Dec 2021 14:23:26 +0100 Subject: [PATCH 0477/2210] Minor renames. --- .../common/viewModel/modelLineProjection.ts | 52 +++++++++---------- .../editor/common/viewModel/viewModelLines.ts | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/vs/editor/common/viewModel/modelLineProjection.ts b/src/vs/editor/common/viewModel/modelLineProjection.ts index 8657011fb93c8..3adc0bf5d1224 100644 --- a/src/vs/editor/common/viewModel/modelLineProjection.ts +++ b/src/vs/editor/common/viewModel/modelLineProjection.ts @@ -19,7 +19,7 @@ export interface IModelLineProjection { */ setVisible(isVisible: boolean): IModelLineProjection; - getLineBreakData(): ModelLineProjectionData | null; + getProjectionData(): ModelLineProjectionData | null; getViewLineCount(): number; getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string; getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number; @@ -63,11 +63,11 @@ export function createModelLineProjection(lineBreakData: ModelLineProjectionData * * inject text */ class ModelLineProjection implements IModelLineProjection { - private readonly _lineBreakData: ModelLineProjectionData; + private readonly projectionData: ModelLineProjectionData; private _isVisible: boolean; constructor(lineBreakData: ModelLineProjectionData, isVisible: boolean) { - this._lineBreakData = lineBreakData; + this.projectionData = lineBreakData; this._isVisible = isVisible; } @@ -80,30 +80,30 @@ class ModelLineProjection implements IModelLineProjection { return this; } - public getLineBreakData(): ModelLineProjectionData | null { - return this._lineBreakData; + public getProjectionData(): ModelLineProjectionData | null { + return this.projectionData; } public getViewLineCount(): number { if (!this._isVisible) { return 0; } - return this._lineBreakData.getOutputLineCount(); + return this.projectionData.getOutputLineCount(); } public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string { this.assertVisible(); // These offsets refer to model text with injected text. - const startOffset = outputLineIndex > 0 ? this._lineBreakData.breakOffsets[outputLineIndex - 1] : 0; - const endOffset = outputLineIndex < this._lineBreakData.breakOffsets.length - ? this._lineBreakData.breakOffsets[outputLineIndex] + const startOffset = outputLineIndex > 0 ? this.projectionData.breakOffsets[outputLineIndex - 1] : 0; + const endOffset = outputLineIndex < this.projectionData.breakOffsets.length + ? this.projectionData.breakOffsets[outputLineIndex] // This case might not be possible anyway, but we clamp the value to be on the safe side. - : this._lineBreakData.breakOffsets[this._lineBreakData.breakOffsets.length - 1]; + : this.projectionData.breakOffsets[this.projectionData.breakOffsets.length - 1]; let r: string; - if (this._lineBreakData.injectionOffsets !== null) { - const injectedTexts = this._lineBreakData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset + 1, this._lineBreakData.injectionOptions![idx], 0)); + if (this.projectionData.injectionOffsets !== null) { + const injectedTexts = this.projectionData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset + 1, this.projectionData.injectionOptions![idx], 0)); r = LineInjectedText.applyInjectedText(model.getLineContent(modelLineNumber), injectedTexts).substring(startOffset, endOffset); } else { r = model.getValueInRange({ @@ -115,7 +115,7 @@ class ModelLineProjection implements IModelLineProjection { } if (outputLineIndex > 0) { - r = spaces(this._lineBreakData.wrappedTextIndentLength) + r; + r = spaces(this.projectionData.wrappedTextIndentLength) + r; } return r; @@ -123,17 +123,17 @@ class ModelLineProjection implements IModelLineProjection { public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { this.assertVisible(); - return this._lineBreakData.getLineLength(outputLineIndex); + return this.projectionData.getLineLength(outputLineIndex); } public getViewLineMinColumn(_model: ITextModel, _modelLineNumber: number, outputLineIndex: number): number { this.assertVisible(); - return this._lineBreakData.getMinOutputOffset(outputLineIndex) + 1; + return this.projectionData.getMinOutputOffset(outputLineIndex) + 1; } public getViewLineMaxColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { this.assertVisible(); - return this._lineBreakData.getMaxOutputOffset(outputLineIndex) + 1; + return this.projectionData.getMaxOutputOffset(outputLineIndex) + 1; } /** @@ -148,7 +148,7 @@ class ModelLineProjection implements IModelLineProjection { public getViewLinesData(model: ISimpleModel, modelLineNumber: number, fromOutputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: Array): void { this.assertVisible(); - const lineBreakData = this._lineBreakData; + const lineBreakData = this.projectionData; const injectionOffsets = lineBreakData.injectionOffsets; const injectionOptions = lineBreakData.injectionOptions; @@ -219,7 +219,7 @@ class ModelLineProjection implements IModelLineProjection { private _getViewLineData(lineWithInjections: LineTokens, inlineDecorations: null | SingleLineInlineDecoration[], outputLineIndex: number): ViewLineData { this.assertVisible(); - const lineBreakData = this._lineBreakData; + const lineBreakData = this.projectionData; const deltaStartIndex = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0); const lineStartOffsetInInputWithInjections = outputLineIndex > 0 ? lineBreakData.breakOffsets[outputLineIndex - 1] : 0; @@ -231,7 +231,7 @@ class ModelLineProjection implements IModelLineProjection { lineContent = spaces(lineBreakData.wrappedTextIndentLength) + lineContent; } - const minColumn = this._lineBreakData.getMinOutputOffset(outputLineIndex) + 1; + const minColumn = this.projectionData.getMinOutputOffset(outputLineIndex) + 1; const maxColumn = lineContent.length + 1; const continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount()); const startVisibleColumn = (outputLineIndex === 0 ? 0 : lineBreakData.breakOffsetsVisibleColumn[outputLineIndex - 1]); @@ -249,30 +249,30 @@ class ModelLineProjection implements IModelLineProjection { public getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number { this.assertVisible(); - return this._lineBreakData.translateToInputOffset(outputLineIndex, outputColumn - 1) + 1; + return this.projectionData.translateToInputOffset(outputLineIndex, outputColumn - 1) + 1; } public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number, affinity: PositionAffinity = PositionAffinity.None): Position { this.assertVisible(); - let r = this._lineBreakData.translateToOutputPosition(inputColumn - 1, affinity); + let r = this.projectionData.translateToOutputPosition(inputColumn - 1, affinity); return r.toPosition(deltaLineNumber); } public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number { this.assertVisible(); - const r = this._lineBreakData.translateToOutputPosition(inputColumn - 1); + const r = this.projectionData.translateToOutputPosition(inputColumn - 1); return deltaLineNumber + r.outputLineIndex; } public normalizePosition(outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position { const baseViewLineNumber = outputPosition.lineNumber - outputLineIndex; - const normalizedOutputPosition = this._lineBreakData.normalizeOutputPosition(outputLineIndex, outputPosition.column - 1, affinity); + const normalizedOutputPosition = this.projectionData.normalizeOutputPosition(outputLineIndex, outputPosition.column - 1, affinity); const result = normalizedOutputPosition.toPosition(baseViewLineNumber); return result; } public getInjectedTextAt(outputLineIndex: number, outputColumn: number): InjectedText | null { - return this._lineBreakData.getInjectedText(outputLineIndex, outputColumn - 1); + return this.projectionData.getInjectedText(outputLineIndex, outputColumn - 1); } private assertVisible() { @@ -301,7 +301,7 @@ class IdentityModelLineProjection implements IModelLineProjection { return HiddenModelLineProjection.INSTANCE; } - public getLineBreakData(): ModelLineProjectionData | null { + public getProjectionData(): ModelLineProjectionData | null { return null; } @@ -387,7 +387,7 @@ class HiddenModelLineProjection implements IModelLineProjection { return IdentityModelLineProjection.INSTANCE; } - public getLineBreakData(): ModelLineProjectionData | null { + public getProjectionData(): ModelLineProjectionData | null { return null; } diff --git a/src/vs/editor/common/viewModel/viewModelLines.ts b/src/vs/editor/common/viewModel/viewModelLines.ts index 2580a4edad02d..3de61885fdf89 100644 --- a/src/vs/editor/common/viewModel/viewModelLines.ts +++ b/src/vs/editor/common/viewModel/viewModelLines.ts @@ -289,7 +289,7 @@ export class ViewModelLinesFromProjectedModel implements IViewModelLines { if (onlyWrappingColumnChanged) { previousLineBreaks = []; for (let i = 0, len = this.modelLineProjections.length; i < len; i++) { - previousLineBreaks[i] = this.modelLineProjections[i].getLineBreakData(); + previousLineBreaks[i] = this.modelLineProjections[i].getProjectionData(); } } From fdbc4f2f0cf25822555342785141fe04dbc60823 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 9 Dec 2021 14:24:00 +0100 Subject: [PATCH 0478/2210] Underscore --- .../common/viewModel/modelLineProjection.ts | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/common/viewModel/modelLineProjection.ts b/src/vs/editor/common/viewModel/modelLineProjection.ts index 3adc0bf5d1224..cf8d35b20d9f3 100644 --- a/src/vs/editor/common/viewModel/modelLineProjection.ts +++ b/src/vs/editor/common/viewModel/modelLineProjection.ts @@ -63,11 +63,11 @@ export function createModelLineProjection(lineBreakData: ModelLineProjectionData * * inject text */ class ModelLineProjection implements IModelLineProjection { - private readonly projectionData: ModelLineProjectionData; + private readonly _projectionData: ModelLineProjectionData; private _isVisible: boolean; constructor(lineBreakData: ModelLineProjectionData, isVisible: boolean) { - this.projectionData = lineBreakData; + this._projectionData = lineBreakData; this._isVisible = isVisible; } @@ -81,29 +81,29 @@ class ModelLineProjection implements IModelLineProjection { } public getProjectionData(): ModelLineProjectionData | null { - return this.projectionData; + return this._projectionData; } public getViewLineCount(): number { if (!this._isVisible) { return 0; } - return this.projectionData.getOutputLineCount(); + return this._projectionData.getOutputLineCount(); } public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string { - this.assertVisible(); + this._assertVisible(); // These offsets refer to model text with injected text. - const startOffset = outputLineIndex > 0 ? this.projectionData.breakOffsets[outputLineIndex - 1] : 0; - const endOffset = outputLineIndex < this.projectionData.breakOffsets.length - ? this.projectionData.breakOffsets[outputLineIndex] + const startOffset = outputLineIndex > 0 ? this._projectionData.breakOffsets[outputLineIndex - 1] : 0; + const endOffset = outputLineIndex < this._projectionData.breakOffsets.length + ? this._projectionData.breakOffsets[outputLineIndex] // This case might not be possible anyway, but we clamp the value to be on the safe side. - : this.projectionData.breakOffsets[this.projectionData.breakOffsets.length - 1]; + : this._projectionData.breakOffsets[this._projectionData.breakOffsets.length - 1]; let r: string; - if (this.projectionData.injectionOffsets !== null) { - const injectedTexts = this.projectionData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset + 1, this.projectionData.injectionOptions![idx], 0)); + if (this._projectionData.injectionOffsets !== null) { + const injectedTexts = this._projectionData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset + 1, this._projectionData.injectionOptions![idx], 0)); r = LineInjectedText.applyInjectedText(model.getLineContent(modelLineNumber), injectedTexts).substring(startOffset, endOffset); } else { r = model.getValueInRange({ @@ -115,25 +115,25 @@ class ModelLineProjection implements IModelLineProjection { } if (outputLineIndex > 0) { - r = spaces(this.projectionData.wrappedTextIndentLength) + r; + r = spaces(this._projectionData.wrappedTextIndentLength) + r; } return r; } public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { - this.assertVisible(); - return this.projectionData.getLineLength(outputLineIndex); + this._assertVisible(); + return this._projectionData.getLineLength(outputLineIndex); } public getViewLineMinColumn(_model: ITextModel, _modelLineNumber: number, outputLineIndex: number): number { - this.assertVisible(); - return this.projectionData.getMinOutputOffset(outputLineIndex) + 1; + this._assertVisible(); + return this._projectionData.getMinOutputOffset(outputLineIndex) + 1; } public getViewLineMaxColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { - this.assertVisible(); - return this.projectionData.getMaxOutputOffset(outputLineIndex) + 1; + this._assertVisible(); + return this._projectionData.getMaxOutputOffset(outputLineIndex) + 1; } /** @@ -146,9 +146,9 @@ class ModelLineProjection implements IModelLineProjection { } public getViewLinesData(model: ISimpleModel, modelLineNumber: number, fromOutputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: Array): void { - this.assertVisible(); + this._assertVisible(); - const lineBreakData = this.projectionData; + const lineBreakData = this._projectionData; const injectionOffsets = lineBreakData.injectionOffsets; const injectionOptions = lineBreakData.injectionOptions; @@ -218,8 +218,8 @@ class ModelLineProjection implements IModelLineProjection { } private _getViewLineData(lineWithInjections: LineTokens, inlineDecorations: null | SingleLineInlineDecoration[], outputLineIndex: number): ViewLineData { - this.assertVisible(); - const lineBreakData = this.projectionData; + this._assertVisible(); + const lineBreakData = this._projectionData; const deltaStartIndex = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength : 0); const lineStartOffsetInInputWithInjections = outputLineIndex > 0 ? lineBreakData.breakOffsets[outputLineIndex - 1] : 0; @@ -231,7 +231,7 @@ class ModelLineProjection implements IModelLineProjection { lineContent = spaces(lineBreakData.wrappedTextIndentLength) + lineContent; } - const minColumn = this.projectionData.getMinOutputOffset(outputLineIndex) + 1; + const minColumn = this._projectionData.getMinOutputOffset(outputLineIndex) + 1; const maxColumn = lineContent.length + 1; const continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount()); const startVisibleColumn = (outputLineIndex === 0 ? 0 : lineBreakData.breakOffsetsVisibleColumn[outputLineIndex - 1]); @@ -248,34 +248,34 @@ class ModelLineProjection implements IModelLineProjection { } public getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number { - this.assertVisible(); - return this.projectionData.translateToInputOffset(outputLineIndex, outputColumn - 1) + 1; + this._assertVisible(); + return this._projectionData.translateToInputOffset(outputLineIndex, outputColumn - 1) + 1; } public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number, affinity: PositionAffinity = PositionAffinity.None): Position { - this.assertVisible(); - let r = this.projectionData.translateToOutputPosition(inputColumn - 1, affinity); + this._assertVisible(); + let r = this._projectionData.translateToOutputPosition(inputColumn - 1, affinity); return r.toPosition(deltaLineNumber); } public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number { - this.assertVisible(); - const r = this.projectionData.translateToOutputPosition(inputColumn - 1); + this._assertVisible(); + const r = this._projectionData.translateToOutputPosition(inputColumn - 1); return deltaLineNumber + r.outputLineIndex; } public normalizePosition(outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position { const baseViewLineNumber = outputPosition.lineNumber - outputLineIndex; - const normalizedOutputPosition = this.projectionData.normalizeOutputPosition(outputLineIndex, outputPosition.column - 1, affinity); + const normalizedOutputPosition = this._projectionData.normalizeOutputPosition(outputLineIndex, outputPosition.column - 1, affinity); const result = normalizedOutputPosition.toPosition(baseViewLineNumber); return result; } public getInjectedTextAt(outputLineIndex: number, outputColumn: number): InjectedText | null { - return this.projectionData.getInjectedText(outputLineIndex, outputColumn - 1); + return this._projectionData.getInjectedText(outputLineIndex, outputColumn - 1); } - private assertVisible() { + private _assertVisible() { if (!this._isVisible) { throw new Error('Not supported'); } From 2858dd87e7d400cdefeb470073e0ec66f39d28b2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Dec 2021 14:14:48 +0100 Subject: [PATCH 0479/2210] html: update constants in typescript mode (for #138677) --- .../server/src/modes/javascriptMode.ts | 187 ++++++++++++------ .../server/src/test/formatting.test.ts | 6 +- 2 files changed, 131 insertions(+), 62 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index e6571c5bac0ae..85b9a8ae2e058 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -387,83 +387,152 @@ function convertRange(document: TextDocument, span: { start: number | undefined, function convertKind(kind: string): CompletionItemKind { switch (kind) { - case 'primitive type': - case 'keyword': + case Kind.primitiveType: + case Kind.keyword: return CompletionItemKind.Keyword; - case 'var': - case 'local var': + + case Kind.const: + case Kind.let: + case Kind.variable: + case Kind.localVariable: + case Kind.alias: + case Kind.parameter: return CompletionItemKind.Variable; - case 'property': - case 'getter': - case 'setter': + + case Kind.memberVariable: + case Kind.memberGetAccessor: + case Kind.memberSetAccessor: return CompletionItemKind.Field; - case 'function': - case 'method': - case 'construct': - case 'call': - case 'index': + + case Kind.function: + case Kind.localFunction: return CompletionItemKind.Function; - case 'enum': + + case Kind.method: + case Kind.constructSignature: + case Kind.callSignature: + case Kind.indexSignature: + return CompletionItemKind.Method; + + case Kind.enum: return CompletionItemKind.Enum; - case 'module': + + case Kind.enumMember: + return CompletionItemKind.EnumMember; + + case Kind.module: + case Kind.externalModuleName: return CompletionItemKind.Module; - case 'class': + + case Kind.class: + case Kind.type: return CompletionItemKind.Class; - case 'interface': + + case Kind.interface: return CompletionItemKind.Interface; - case 'warning': + + case Kind.warning: + return CompletionItemKind.Text; + + case Kind.script: return CompletionItemKind.File; - } - return CompletionItemKind.Property; + case Kind.directory: + return CompletionItemKind.Folder; + + case Kind.string: + return CompletionItemKind.Constant; + + default: + return CompletionItemKind.Property; + } +} +const enum Kind { + alias = 'alias', + callSignature = 'call', + class = 'class', + const = 'const', + constructorImplementation = 'constructor', + constructSignature = 'construct', + directory = 'directory', + enum = 'enum', + enumMember = 'enum member', + externalModuleName = 'external module name', + function = 'function', + indexSignature = 'index', + interface = 'interface', + keyword = 'keyword', + let = 'let', + localFunction = 'local function', + localVariable = 'local var', + method = 'method', + memberGetAccessor = 'getter', + memberSetAccessor = 'setter', + memberVariable = 'property', + module = 'module', + primitiveType = 'primitive type', + script = 'script', + type = 'type', + variable = 'var', + warning = 'warning', + string = 'string', + parameter = 'parameter', + typeParameter = 'type parameter' } function convertSymbolKind(kind: string): SymbolKind { switch (kind) { - case 'var': - case 'local var': - case 'const': - return SymbolKind.Variable; - case 'function': - case 'local function': - return SymbolKind.Function; - case 'enum': - return SymbolKind.Enum; - case 'module': - return SymbolKind.Module; - case 'class': - return SymbolKind.Class; - case 'interface': - return SymbolKind.Interface; - case 'method': - return SymbolKind.Method; - case 'property': - case 'getter': - case 'setter': - return SymbolKind.Property; + case Kind.module: return SymbolKind.Module; + case Kind.class: return SymbolKind.Class; + case Kind.enum: return SymbolKind.Enum; + case Kind.enumMember: return SymbolKind.EnumMember; + case Kind.interface: return SymbolKind.Interface; + case Kind.indexSignature: return SymbolKind.Method; + case Kind.callSignature: return SymbolKind.Method; + case Kind.method: return SymbolKind.Method; + case Kind.memberVariable: return SymbolKind.Property; + case Kind.memberGetAccessor: return SymbolKind.Property; + case Kind.memberSetAccessor: return SymbolKind.Property; + case Kind.variable: return SymbolKind.Variable; + case Kind.let: return SymbolKind.Variable; + case Kind.const: return SymbolKind.Variable; + case Kind.localVariable: return SymbolKind.Variable; + case Kind.alias: return SymbolKind.Variable; + case Kind.function: return SymbolKind.Function; + case Kind.localFunction: return SymbolKind.Function; + case Kind.constructSignature: return SymbolKind.Constructor; + case Kind.constructorImplementation: return SymbolKind.Constructor; + case Kind.typeParameter: return SymbolKind.TypeParameter; + case Kind.string: return SymbolKind.String; + default: return SymbolKind.Variable; } - return SymbolKind.Variable; } -function convertOptions(options: FormattingOptions, formatSettings: any, initialIndentLevel: number): ts.FormatCodeOptions { +function convertOptions(options: FormattingOptions, formatSettings: any, initialIndentLevel: number): ts.FormatCodeSettings { return { - ConvertTabsToSpaces: options.insertSpaces, - TabSize: options.tabSize, - IndentSize: options.tabSize, - IndentStyle: ts.IndentStyle.Smart, - NewLineCharacter: '\n', - BaseIndentSize: options.tabSize * initialIndentLevel, - InsertSpaceAfterCommaDelimiter: Boolean(!formatSettings || formatSettings.insertSpaceAfterCommaDelimiter), - InsertSpaceAfterSemicolonInForStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterSemicolonInForStatements), - InsertSpaceBeforeAndAfterBinaryOperators: Boolean(!formatSettings || formatSettings.insertSpaceBeforeAndAfterBinaryOperators), - InsertSpaceAfterKeywordsInControlFlowStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterKeywordsInControlFlowStatements), - InsertSpaceAfterFunctionKeywordForAnonymousFunctions: Boolean(!formatSettings || formatSettings.insertSpaceAfterFunctionKeywordForAnonymousFunctions), - InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis), - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets), - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces), - InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces), - PlaceOpenBraceOnNewLineForControlBlocks: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForFunctions), - PlaceOpenBraceOnNewLineForFunctions: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForControlBlocks) + convertTabsToSpaces: options.insertSpaces, + tabSize: options.tabSize, + indentSize: options.tabSize, + indentStyle: ts.IndentStyle.Smart, + newLineCharacter: '\n', + baseIndentSize: options.tabSize * initialIndentLevel, + insertSpaceAfterCommaDelimiter: Boolean(!formatSettings || formatSettings.insertSpaceAfterCommaDelimiter), + insertSpaceAfterConstructor: Boolean(formatSettings && formatSettings.insertSpaceAfterConstructor), + insertSpaceAfterSemicolonInForStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterSemicolonInForStatements), + insertSpaceBeforeAndAfterBinaryOperators: Boolean(!formatSettings || formatSettings.insertSpaceBeforeAndAfterBinaryOperators), + insertSpaceAfterKeywordsInControlFlowStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterKeywordsInControlFlowStatements), + insertSpaceAfterFunctionKeywordForAnonymousFunctions: Boolean(!formatSettings || formatSettings.insertSpaceAfterFunctionKeywordForAnonymousFunctions), + insertSpaceBeforeFunctionParenthesis: Boolean(formatSettings && formatSettings.insertSpaceBeforeFunctionParenthesis), + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis), + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets), + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces), + insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: Boolean(!formatSettings || formatSettings.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces), + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces), + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces), + insertSpaceAfterTypeAssertion: Boolean(formatSettings && formatSettings.insertSpaceAfterTypeAssertion), + placeOpenBraceOnNewLineForControlBlocks: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForFunctions), + placeOpenBraceOnNewLineForFunctions: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForControlBlocks), + semicolons: formatSettings?.semicolons }; } diff --git a/extensions/html-language-features/server/src/test/formatting.test.ts b/extensions/html-language-features/server/src/test/formatting.test.ts index 519ace5f9c6bb..140942868e443 100644 --- a/extensions/html-language-features/server/src/test/formatting.test.ts +++ b/extensions/html-language-features/server/src/test/formatting.test.ts @@ -77,7 +77,7 @@ suite('HTML Embedded Formatting', () => { }); test('HTML & Multiple Scripts', async () => { - await assertFormat('\n', '\n\n\n \n \n\n\n'); + await assertFormat('\n', '\n\n\n \n \n\n\n'); }); test('HTML & Styles', async () => { @@ -120,7 +120,7 @@ suite('HTML Embedded Formatting', () => { '', '', ' ${coreDependencies} +
    +
    `; } @@ -1261,6 +1268,55 @@ var requirejs = (function() { }, 50); } + async find(query: string): Promise { + if (query === '') { + return []; + } + + const p = new Promise(resolve => { + const sub = this.webview?.onMessage(e => { + if (e.message.type === 'didFind') { + resolve(e.message.matches); + sub?.dispose(); + } + }); + }); + + this._sendMessageToWebview({ + type: 'find', + query: query + }); + + const ret = await p; + return ret; + } + + findStop() { + this._sendMessageToWebview({ + type: 'findStop' + }); + } + + async findHighlight(index: number): Promise { + const p = new Promise(resolve => { + const sub = this.webview?.onMessage(e => { + if (e.message.type === 'didFindHighlight') { + resolve(e.message.offset); + sub?.dispose(); + } + }); + }); + + this._sendMessageToWebview({ + type: 'findHighlight', + index + }); + + const ret = await p; + return ret; + } + + deltaCellOutputContainerClassNames(cellId: string, added: string[], removed: string[]) { this._sendMessageToWebview({ type: 'decorations', diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index ed62d87c052b9..e55c2cbcec26b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -364,6 +364,38 @@ export interface ITokenizedStylesChangedMessage { readonly css: string; } +export interface IFindMessage { + readonly type: 'find'; + readonly query: string; +} + + +export interface IFindHighlightMessage { + readonly type: 'findHighlight'; + readonly index: number; +} + +export interface IFindStopMessage { + readonly type: 'findStop'; +} + +export interface IFindMatch { + readonly type: 'preview' | 'output'; + readonly cellId: string; + readonly id: string; + readonly index: number; +} + +export interface IDidFindMessage extends BaseToWebviewMessage { + readonly type: 'didFind'; + readonly matches: IFindMatch[]; +} + +export interface IDidFindHighlightMessage extends BaseToWebviewMessage { + readonly type: 'didFindHighlight'; + readonly offset: number; +} + export type FromWebviewMessage = WebviewInitialized | IDimensionMessage | IMouseEnterMessage | @@ -390,7 +422,9 @@ export type FromWebviewMessage = WebviewInitialized | IInitializedMarkupMessage | IRenderedMarkupMessage | ITelemetryFoundRenderedMarkdownMath | - ITelemetryFoundUnrenderedMarkdownMath; + ITelemetryFoundUnrenderedMarkdownMath | + IDidFindMessage | + IDidFindHighlightMessage; export type ToWebviewMessage = IClearMessage | IFocusOutputMessage | @@ -416,6 +450,9 @@ export type ToWebviewMessage = IClearMessage | INotebookOptionsMessage | INotebookUpdateWorkspaceTrust | ITokenizedCodeBlockMessage | - ITokenizedStylesChangedMessage; + ITokenizedStylesChangedMessage | + IFindMessage | + IFindHighlightMessage | + IFindStopMessage; export type AnyMessage = FromWebviewMessage | ToWebviewMessage; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 5bc55ad574d13..d490b389133f7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -533,6 +533,213 @@ async function webviewPreloads(ctx: PreloadContext) { window.addEventListener('wheel', handleWheel); + let _findingMatches: { + type: any; + cellId: string; + id: string; + container: any; + range: any; + }[] = []; + let _findMatchIndex = -1; + let matchColor = window.getComputedStyle(document.getElementById('_defaultColorPalatte')!).color; + let currentMatchColor = window.getComputedStyle(document.getElementById('_defaultColorPalatte')!).backgroundColor; + + const find = (query: string) => { + let find = true; + let matches = []; + + let range = document.createRange(); + range.selectNodeContents(document.getElementById('findStart')!); + let sel = window.getSelection(); + sel?.removeAllRanges(); + sel?.addRange(range); + + while (find && matches.length < 200) { + find = (window as any).find(query, /* caseSensitive*/ false, + /* backwards*/ false, + /* wrapAround*/ false, + /* wholeWord */ false, + /* searchInFrames*/ true, + false); + + if (find) { + const selection = window.getSelection(); + if (!selection) { + console.log('no selection'); + break; + } + + const anchorNode = selection?.anchorNode?.parentElement; + + if (anchorNode) { + const lastEl: any = matches.length ? matches[matches.length - 1] : null; + + if (lastEl && lastEl.container.contains(anchorNode)) { + document.designMode = 'On'; + // document.execCommand('hiliteColor', false, '#ff0000'); + window.document.execCommand('hiliteColor', false, matchColor); + document.designMode = 'Off'; + + const range = window.getSelection()!.getRangeAt(0).cloneRange(); + matches.push({ + type: lastEl.type, + id: lastEl.id, + cellId: lastEl.cellId, + container: lastEl.container, + range: { + collapsed: range.collapsed, + commonAncestorContainer: range.commonAncestorContainer, + endContainer: range.endContainer, + endOffset: range.endOffset, + startContainer: range.startContainer, + startOffset: range.startOffset + } + }); + + } else { + for (let node = anchorNode as Element | null; node; node = node.parentElement) { + if (!(node instanceof Element)) { + break; + } + + if (node.classList.contains('output')) { + // inside output + const cellId = node.parentElement?.parentElement?.id; + if (cellId) { + document.designMode = 'On'; + // document.execCommand('hiliteColor', false, '#ff0000'); + window.document.execCommand('hiliteColor', false, matchColor); + document.designMode = 'Off'; + + const range = window.getSelection()!.getRangeAt(0); + + matches.push({ + type: 'output', + id: node.id, + cellId: cellId, + container: node, + range: { + collapsed: range.collapsed, + commonAncestorContainer: range.commonAncestorContainer, + endContainer: range.endContainer, + endOffset: range.endOffset, + startContainer: range.startContainer, + startOffset: range.startOffset + } + }); + + } + break; + } + + if (node.id === 'container' || node === document.body) { + break; + } + } + } + + } else { + break; + } + } + } + + _findingMatches = matches; + postNotebookMessage('didFind', { + matches: matches.map((match, index) => ({ + type: match.type, + id: match.id, + cellId: match.cellId, + index + })) + }); + }; + + const highlightCurrentMatch = (index: number) => { + const oldMatch = _findingMatches[_findMatchIndex]; + if (oldMatch) { + const sel = window.getSelection(); + if (sel) { + try { + sel.removeAllRanges(); + const r = document.createRange(); + r.setStart(oldMatch.range.startContainer, oldMatch.range.startOffset); + r.setEnd(oldMatch.range.endContainer, oldMatch.range.endOffset); + sel.addRange(r); + document.designMode = 'On'; + document.execCommand('removeFormat', false, undefined); + window.document.execCommand('hiliteColor', false, matchColor); + document.designMode = 'Off'; + + sel.removeAllRanges(); + } catch (e) { + console.log(e); + } + } + } + const match = _findingMatches[index]; + _findMatchIndex = index; + const sel = window.getSelection(); + if (!!match && !!sel) { + // const outputOffset = match.cellId + let offset = 0; + try { + const outputOffset = document.getElementById(match.id)!.getBoundingClientRect().top; + const tempRange = document.createRange(); + tempRange.selectNode(match.range.startContainer); + const rangeOffset = tempRange.getBoundingClientRect().top; + tempRange.detach(); + offset = rangeOffset - outputOffset; + } catch (e) { + } + + try { + sel.removeAllRanges(); + const r = document.createRange(); + r.setStart(match.range.startContainer, match.range.startOffset); + r.setEnd(match.range.endContainer, match.range.endOffset); + sel.addRange(r); + document.designMode = 'On'; + window.document.execCommand('hiliteColor', false, currentMatchColor); + document.designMode = 'Off'; + + sel.removeAllRanges(); + + postNotebookMessage('didFindHighlight', { + offset + }); + } catch (e) { + console.log(e); + } + } + }; + + const clearFindMatches = () => { + _findingMatches.forEach(match => { + const sel = window.getSelection(); + if (sel) { + try { + sel.removeAllRanges(); + const r = document.createRange(); + r.setStart(match.range.startContainer, match.range.startOffset); + r.setEnd(match.range.endContainer, match.range.endOffset); + sel.addRange(r); + document.designMode = 'On'; + // document.execCommand('hiliteColor', false, '#ff0000'); + document.execCommand('removeFormat', false, undefined); + document.designMode = 'Off'; + + sel.removeAllRanges(); + } catch (e) { + console.log(e); + } + } + }); + + _findingMatches = []; + _findMatchIndex = -1; + }; + window.addEventListener('message', async rawEvent => { const event = rawEvent as ({ data: webviewMessages.ToWebviewMessage; }); @@ -694,6 +901,19 @@ async function webviewPreloads(ctx: PreloadContext) { } break; } + case 'find': { + clearFindMatches(); + find(event.data.query); + break; + } + case 'findHighlight': { + highlightCurrentMatch(event.data.index); + break; + } + case 'findStop': { + clearFindMatches(); + break; + } } }); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 84a5bbbf7dd9f..11ee194b7f902 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -436,7 +436,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod return { cell: this, - matches + matches, + modelMatchCount: matches.length }; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index fc3ec03388a18..2c0e8214bd660 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { FindMatch, IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/editor/common/model/editStack'; import { IntervalNode, IntervalTree } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -23,7 +23,7 @@ import { FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { CellFoldingState, EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; -import { CellEditState, CellFindMatch, CellFindMatchWithIndex, ICellViewModel, INotebookDeltaCellStatusBarItems, INotebookDeltaDecoration, NotebookLayoutInfo, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFindMatch, CellFindMatchWithIndex, ICellViewModel, INotebookDeltaCellStatusBarItems, INotebookDeltaDecoration, NotebookLayoutInfo, NotebookMetadataChangedEvent, OutputFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellSelectionCollection } from 'vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; @@ -909,7 +909,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD matches.push({ cell: cellMatches.cell, index: index, - matches: cellMatches.matches + matches: cellMatches.matches, + modelMatchCount: cellMatches.matches.length }); } }); @@ -938,10 +939,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD matches.forEach(match => { match.matches.forEach((singleMatch, index) => { - textEdits.push({ - edit: { range: singleMatch.range, text: texts[index] }, - resource: match.cell.uri - }); + if ((singleMatch as OutputFindMatch).index !== undefined) { + textEdits.push({ + edit: { range: (singleMatch as FindMatch).range, text: texts[index] }, + resource: match.cell.uri + }); + } }); }); From d5c15dd8425d0b52dc01151ded7b696787cdf491 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 30 Dec 2021 15:50:51 -0800 Subject: [PATCH 0924/2210] debug: open the hex editor when reading memory --- .../contrib/debug/browser/debugMemory.ts | 2 +- .../contrib/debug/browser/variablesView.ts | 25 +++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugMemory.ts b/src/vs/workbench/contrib/debug/browser/debugMemory.ts index b76e7d4d1bffa..b48f556a114ff 100644 --- a/src/vs/workbench/contrib/debug/browser/debugMemory.ts +++ b/src/vs/workbench/contrib/debug/browser/debugMemory.ts @@ -200,7 +200,7 @@ export class DebugMemoryFileSystemProvider implements IFileSystemProvider { offset, readOnly: !!session.capabilities.supportsWriteMemoryRequest, sessionId: uri.authority, - memoryReference, + memoryReference: decodeURIComponent(memoryReference), }; } } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 70bdcd58d1058..837a447ae9f10 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -32,11 +32,13 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { IMenuService, IMenu, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { localize } from 'vs/nls'; import { Codicon } from 'vs/base/common/codicons'; import { coalesce } from 'vs/base/common/arrays'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const $ = dom.$; let forgetScopes = true; @@ -475,6 +477,10 @@ CommandsRegistry.registerCommand({ }); export const VIEW_MEMORY_ID = 'workbench.debug.viewlet.action.viewMemory'; + +const HEX_EDITOR_EXTENSION_ID = 'ms-vscode.hexeditor'; +const HEX_EDITOR_EDITOR_ID = 'hexEditor.hexedit'; + CommandsRegistry.registerCommand({ id: VIEW_MEMORY_ID, handler: async (accessor: ServicesAccessor, arg: IVariablesContext, ctx?: (Variable | Expression)[]) => { @@ -482,10 +488,19 @@ CommandsRegistry.registerCommand({ return; } - accessor.get(IOpenerService).open(getUriForDebugMemory( - arg.sessionId, - arg.variable.memoryReference, - )); + const commandService = accessor.get(ICommandService); + const editorService = accessor.get(IEditorService); + const ext = await accessor.get(IExtensionService).getExtension(HEX_EDITOR_EXTENSION_ID); + if (!ext) { + await commandService.executeCommand('workbench.extensions.search', `@id:${HEX_EDITOR_EXTENSION_ID}`); + } else { + await editorService.openEditor({ + resource: getUriForDebugMemory(arg.sessionId, arg.variable.memoryReference), + options: { + override: HEX_EDITOR_EDITOR_ID, + }, + }); + } } }); From 2933672219cdeb4e4fc04134f332fd2903bb2c56 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Dec 2021 15:51:44 -0800 Subject: [PATCH 0925/2210] Fix notebook test --- .../src/singlefolder-tests/notebook.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 0997367b60a30..2a6df581c3372 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -873,7 +873,7 @@ suite('Notebook API tests', function () { }); - test.skip('execution cancelled when delete while executing', async () => { + test('execution cancelled when delete while executing', async () => { const document = await openRandomNotebookDocument(); const cell = document.cellAt(0); @@ -891,9 +891,8 @@ suite('Notebook API tests', function () { }; testDisposables.push(cancelledKernel.controller); - const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - await assertKernel(cancelledKernel, notebook); + await vscode.window.showNotebookDocument(document); + await assertKernel(cancelledKernel, document); await vscode.commands.executeCommand('notebook.cell.execute'); // Delete executing cell From 633c9465ae6ea6202f3a57594a83c4d6412855cd Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Dec 2021 16:01:04 -0800 Subject: [PATCH 0926/2210] Move getSelectedOrSuggestedKernel to kernel service --- .../notebook/browser/notebookEditorWidget.ts | 2 +- .../browser/notebookExecutionServiceImpl.ts | 15 ++++----------- .../notebook/browser/notebookKernelServiceImpl.ts | 5 +++++ .../notebook/common/notebookExecutionService.ts | 2 -- .../notebook/common/notebookKernelService.ts | 5 +++++ 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 88108b67fefc1..55d71bc55fba7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2068,7 +2068,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } get activeKernel() { - return this.textModel && this.notebookExecutionService.getSelectedOrSuggestedKernel(this.textModel); + return this.textModel && this.notebookKernelService.getSelectedOrSuggestedKernel(this.textModel); } async cancelNotebookCells(cells?: Iterable): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index a62ada9faf757..5bbdd52306714 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -12,7 +12,7 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode import { CellKind, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export class NotebookExecutionService implements INotebookExecutionService { declare _serviceBrand: undefined; @@ -26,13 +26,6 @@ export class NotebookExecutionService implements INotebookExecutionService { ) { } - getSelectedOrSuggestedKernel(notebook: INotebookTextModel): INotebookKernel | undefined { - // TODO later can be inlined in notebookEditorWidget - // returns SELECTED or the ONLY available kernel - const info = this._notebookKernelService.getMatchingKernel(notebook); - return info.selected ?? (info.all.length === 1 ? info.all[0] : undefined); - } - async executeNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise { const cellsArr = Array.from(cells); this._logService.debug(`NotebookExecutionService#executeNotebookCells ${JSON.stringify(cellsArr.map(c => c.handle))}`); @@ -42,10 +35,10 @@ export class NotebookExecutionService implements INotebookExecutionService { return; } - let kernel = this.getSelectedOrSuggestedKernel(notebook); + let kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); if (!kernel) { await this._commandService.executeCommand(SELECT_KERNEL_ID); - kernel = this.getSelectedOrSuggestedKernel(notebook); + kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); } if (!kernel) { @@ -73,7 +66,7 @@ export class NotebookExecutionService implements INotebookExecutionService { async cancelNotebookCellHandles(notebook: INotebookTextModel, cells: Iterable): Promise { const cellsArr = Array.from(cells); this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`); - const kernel = this.getSelectedOrSuggestedKernel(notebook); + const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); if (kernel) { await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts index a576297af56cb..c87b6bb596aa7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -194,6 +194,11 @@ export class NotebookKernelService extends Disposable implements INotebookKernel return { all, selected, suggestions }; } + getSelectedOrSuggestedKernel(notebook: INotebookTextModel): INotebookKernel | undefined { + const info = this.getMatchingKernel(notebook); + return info.selected ?? (info.all.length === 1 ? info.all[0] : undefined); + } + // default kernel for notebookType selectKernelForNotebookType(kernel: INotebookKernel, typeId: string): void { const existing = this._typeBindings.get(typeId); diff --git a/src/vs/workbench/contrib/notebook/common/notebookExecutionService.ts b/src/vs/workbench/contrib/notebook/common/notebookExecutionService.ts index 329f984d40211..50b2623c17c56 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookExecutionService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookExecutionService.ts @@ -6,7 +6,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { INotebookTextModel, IOutputDto, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export enum CellExecutionUpdateType { Output = 1, @@ -33,7 +32,6 @@ export const INotebookExecutionService = createDecorator): Promise; cancelNotebookCells(notebook: INotebookTextModel, cells: Iterable): Promise; cancelNotebookCellHandles(notebook: INotebookTextModel, cells: Iterable): Promise; diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index cc406fe59cff1..85c1ab380fcc6 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -70,6 +70,11 @@ export interface INotebookKernelService { getMatchingKernel(notebook: INotebookTextModelLike): INotebookKernelMatchResult; + /** + * Returns the selected or only available kernel. + */ + getSelectedOrSuggestedKernel(notebook: INotebookTextModelLike): INotebookKernel | undefined; + /** * Bind a notebook document to a kernel. A notebook is only bound to one kernel * but a kernel can be bound to many notebooks (depending on its configuration) From 3e70603d63d3dca48f7d587a4118521af7be8013 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Dec 2021 16:39:16 -0800 Subject: [PATCH 0927/2210] Fix #139345 --- src/vs/workbench/contrib/search/common/searchModel.ts | 4 ++-- src/vs/workbench/services/search/common/searchService.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index b0892575c527a..160a46f7ddbbc 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -280,7 +280,7 @@ export class FileMatch extends Disposable implements IFileMatch { const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; const matches = this._model - .findMatches(this._query.pattern, this._model.getFullModelRange(), !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults); + .findMatches(this._query.pattern, this._model.getFullModelRange(), !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); this.updateMatches(matches, true); } @@ -300,7 +300,7 @@ export class FileMatch extends Disposable implements IFileMatch { oldMatches.forEach(match => this._matches.delete(match.id())); const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; - const matches = this._model.findMatches(this._query.pattern, range, !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults); + const matches = this._model.findMatches(this._query.pattern, range, !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); this.updateMatches(matches, modelChange); } diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index dd38d304382f8..20f0bde4152f3 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -22,6 +22,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { isNumber } from 'vs/base/common/types'; export class SearchService extends Disposable implements ISearchService { @@ -445,7 +446,7 @@ export class SearchService extends Disposable implements ISearchService { } // Use editor API to find matches - const askMax = typeof query.maxResults === 'number' ? query.maxResults + 1 : undefined; + const askMax = isNumber(query.maxResults) ? query.maxResults + 1 : Number.MAX_SAFE_INTEGER; let matches = model.findMatches(query.contentPattern.pattern, false, !!query.contentPattern.isRegExp, !!query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators! : null, false, askMax); if (matches.length) { if (askMax && matches.length >= askMax) { From 108a8d5925b4ec018f672543437df72d96e06a1e Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Thu, 30 Dec 2021 22:01:42 -0500 Subject: [PATCH 0928/2210] Fixes #139939 (#139940) --- .../contrib/terminal/browser/terminal.web.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.web.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.web.contribution.ts index 3c0f5fc5ecf31..6818669dbe746 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.web.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.web.contribution.ts @@ -8,6 +8,7 @@ import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/co import { ITerminalProfileResolverService, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { BrowserTerminalProfileResolverService } from 'vs/workbench/contrib/terminal/browser/terminalProfileResolverService'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; registerSingleton(ITerminalProfileResolverService, BrowserTerminalProfileResolverService, true); @@ -16,6 +17,6 @@ registerSingleton(ITerminalProfileResolverService, BrowserTerminalProfileResolve KeybindingsRegistry.registerKeybindingRule({ id: TerminalCommandId.New, weight: KeybindingWeight.WorkbenchContrib, - when: undefined, + when: TerminalContextKeys.notFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC }); From 0f265a3651ab227764e30dfe50ae967d34e5890d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Dec 2021 08:14:39 +0100 Subject: [PATCH 0929/2210] integration tests - skip flakes https://github.com/microsoft/vscode/issues/139960 https://github.com/microsoft/vscode/issues/139958 --- .../src/singlefolder-tests/notebook.editor.test.ts | 2 +- .../vscode-api-tests/src/singlefolder-tests/webview.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index 8b2a1f2459b97..71577a1122b1e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -102,7 +102,7 @@ suite('Notebook Editor', function () { assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); }); - test('Notebook Editor Event - onDidChangeVisibleNotebookEditors on open/close', async function () { + test.skip('Notebook Editor Event - onDidChangeVisibleNotebookEditors on open/close', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139958 const openedEditor = utils.asPromise(vscode.window.onDidChangeVisibleNotebookEditors); const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); await vscode.window.showNotebookDocument(resource); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 9b7452ddd5327..66b502101c87a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -242,7 +242,7 @@ suite('vscode API - webview', () => { }); - test('webviews should only be able to load resources from workspace by default', async () => { + test.skip('webviews should only be able to load resources from workspace by default', async () => { // TODO@mjbvz https://github.com/microsoft/vscode/issues/139960 const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { From 9074a95bed0020ebe7e77a251d85cfdfc6212e7d Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 31 Dec 2021 04:09:53 -0800 Subject: [PATCH 0930/2210] Don't involve pure modifier keys when resolving if in-chord (#139254) Fixes #139152 --- src/vs/platform/list/browser/listService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 3c14449399f0c..4482fc5f08330 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -800,6 +800,10 @@ function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingS let inChord = false; return event => { + if (event.toKeybinding().isModifierKey()) { + return false; + } + if (inChord) { inChord = false; return false; From 99305570d885bce929ba8f24d90f2c3ee54c5c2f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 31 Dec 2021 15:22:07 +0100 Subject: [PATCH 0931/2210] fix: :bug: update distro Closes: #139144 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1aed68be832ea..a47082a175b0b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.64.0", - "distro": "1f70d5b8b91ef1ab0ebf90665d4222a0bf4bd979", + "distro": "553bb9d3803948fcccf1a35374942d8aba9c9c73", "author": { "name": "Microsoft Corporation" }, @@ -228,4 +228,4 @@ "elliptic": "^6.5.3", "nwmatcher": "^1.4.4" } -} +} \ No newline at end of file From a8501a073ca04d653f1b05f54d47764b055186b8 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 31 Dec 2021 11:09:08 -0800 Subject: [PATCH 0932/2210] debug: support writeMemory requests --- .../contrib/debug/browser/debugMemory.ts | 53 +++++++++++++++++-- .../contrib/debug/browser/rawDebugSession.ts | 4 +- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugMemory.ts b/src/vs/workbench/contrib/debug/browser/debugMemory.ts index b48f556a114ff..ea17e487be4b6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugMemory.ts +++ b/src/vs/workbench/contrib/debug/browser/debugMemory.ts @@ -5,11 +5,12 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; -import { toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { clamp } from 'vs/base/common/numbers'; import { assertNever } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FileChangeType, FileOpenOptions, FilePermission, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileChange, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; -import { DEBUG_MEMORY_SCHEME, IDebugService, IDebugSession, IMemoryRegion, MemoryRangeType } from 'vs/workbench/contrib/debug/common/debug'; +import { DEBUG_MEMORY_SCHEME, IDebugService, IDebugSession, IMemoryInvalidationEvent, IMemoryRegion, MemoryRange, MemoryRangeType } from 'vs/workbench/contrib/debug/common/debug'; const rangeRe = /range=([0-9]+):([0-9]+)/; @@ -86,9 +87,14 @@ export class DebugMemoryFileSystemProvider implements IFileSystemProvider { /** @inheritdoc */ public open(resource: URI, _opts: FileOpenOptions): Promise { - const { session, memoryReference } = this.parseUri(resource); + const { session, memoryReference, offset } = this.parseUri(resource); const fd = this.memoryFdCounter++; - this.fdMemory.set(fd, { session, region: session.getMemory(memoryReference) }); + let region = session.getMemory(memoryReference); + if (offset) { + region = new MemoryRegionView(region, offset); + } + + this.fdMemory.set(fd, { session, region }); return Promise.resolve(fd); } @@ -198,9 +204,46 @@ export class DebugMemoryFileSystemProvider implements IFileSystemProvider { return { session, offset, - readOnly: !!session.capabilities.supportsWriteMemoryRequest, + readOnly: !session.capabilities.supportsWriteMemoryRequest, sessionId: uri.authority, memoryReference: decodeURIComponent(memoryReference), }; } } + +/** A wrapper for a MemoryRegion that references a subset of data in another region. */ +class MemoryRegionView extends Disposable implements IMemoryRegion { + private readonly invalidateEmitter = new Emitter(); + + public readonly onDidInvalidate = this.invalidateEmitter.event; + public readonly writable: boolean; + private readonly width = this.range.toOffset - this.range.fromOffset; + + constructor(private readonly parent: IMemoryRegion, public readonly range: { fromOffset: number; toOffset: number }) { + super(); + this.writable = parent.writable; + + this._register(parent.onDidInvalidate(e => { + const fromOffset = clamp(e.fromOffset - range.fromOffset, 0, this.width); + const toOffset = clamp(e.toOffset - range.fromOffset, 0, this.width); + if (toOffset > fromOffset) { + this.invalidateEmitter.fire({ fromOffset, toOffset }); + } + })); + } + + public read(fromOffset: number, toOffset: number): Promise { + if (fromOffset < 0) { + throw new RangeError(`Invalid fromOffset: ${fromOffset}`); + } + + return this.parent.read( + this.range.fromOffset + fromOffset, + this.range.fromOffset + Math.min(toOffset, this.width), + ); + } + + public write(offset: number, data: VSBuffer): Promise { + return this.parent.write(this.range.fromOffset + offset, data); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 2487937719abb..87fd439a7d2b6 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -526,7 +526,7 @@ export class RawDebugSession implements IDisposable { return await this.send('readMemory', args); } - return Promise.reject(new Error('disassemble is not supported')); + return Promise.reject(new Error('readMemory is not supported')); } async writeMemory(args: DebugProtocol.WriteMemoryArguments): Promise { @@ -534,7 +534,7 @@ export class RawDebugSession implements IDisposable { return await this.send('writeMemory', args); } - return Promise.reject(new Error('disassemble is not supported')); + return Promise.reject(new Error('writeMemory is not supported')); } cancel(args: DebugProtocol.CancelArguments): Promise { From 25594310c07af385d21c5fabc7bb3bd70d92175b Mon Sep 17 00:00:00 2001 From: Nafiur Rahman Khadem Date: Sat, 1 Jan 2022 05:41:59 +0000 Subject: [PATCH 0933/2210] Fix Page Up/Down in notebook editor cells --- .../editor/browser/controller/coreCommands.ts | 2 +- .../browser/contrib/navigation/arrow.ts | 109 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 59023be0705c0..fb55362511fe0 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -598,7 +598,7 @@ export namespace CoreNavigationCommands { direction: this._staticArgs.direction, unit: this._staticArgs.unit, select: this._staticArgs.select, - value: viewModel.cursorConfig.pageSize + value: dynamicArgs.pageSize || viewModel.cursorConfig.pageSize }; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index d6ea5ca6b2236..8d378fbcf0f03 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -16,6 +16,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; const NOTEBOOK_FOCUS_BOTTOM = 'notebook.focusBottom'; @@ -24,6 +25,10 @@ const NOTEBOOK_FOCUS_NEXT_EDITOR = 'notebook.focusNextEditor'; const FOCUS_IN_OUTPUT_COMMAND_ID = 'notebook.cell.focusInOutput'; const FOCUS_OUT_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutOutput'; export const CENTER_ACTIVE_CELL = 'notebook.centerActiveCell'; +const NOTEBOOK_CURSOR_PAGEUP_COMMAND_ID = 'notebook.cell.cursorPageUp'; +const NOTEBOOK_CURSOR_PAGEUP_SELECT_COMMAND_ID = 'notebook.cell.cursorPageUpSelect'; +const NOTEBOOK_CURSOR_PAGEDOWN_COMMAND_ID = 'notebook.cell.cursorPageDown'; +const NOTEBOOK_CURSOR_PAGEDOWN_SELECT_COMMAND_ID = 'notebook.cell.cursorPageDownSelect'; registerAction2(class extends NotebookCellAction { @@ -234,6 +239,110 @@ registerAction2(class CenterActiveCellAction extends NotebookCellAction { } }); +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: NOTEBOOK_CURSOR_PAGEUP_COMMAND_ID, + title: localize('cursorPageUp', "Cell Cursor Page Up"), + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + ), + primary: KeyCode.PageUp, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + } + ] + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + EditorExtensionsRegistry.getEditorCommand('cursorPageUp').runCommand(accessor, { pageSize: getPageSize(context) }); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: NOTEBOOK_CURSOR_PAGEUP_SELECT_COMMAND_ID, + title: localize('cursorPageUpSelect', "Cell Cursor Page Up Select"), + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + ), + primary: KeyMod.Shift | KeyCode.PageUp, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + } + ] + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + EditorExtensionsRegistry.getEditorCommand('cursorPageUpSelect').runCommand(accessor, { pageSize: getPageSize(context) }); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: NOTEBOOK_CURSOR_PAGEDOWN_COMMAND_ID, + title: localize('cursorPageDown', "Cell Cursor Page Down"), + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + ), + primary: KeyCode.PageDown, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + } + ] + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + EditorExtensionsRegistry.getEditorCommand('cursorPageDown').runCommand(accessor, { pageSize: getPageSize(context) }); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: NOTEBOOK_CURSOR_PAGEDOWN_SELECT_COMMAND_ID, + title: localize('cursorPageDownSelect', "Cell Cursor Page Down Select"), + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + ), + primary: KeyMod.Shift | KeyCode.PageDown, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + } + ] + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + EditorExtensionsRegistry.getEditorCommand('cursorPageDownSelect').runCommand(accessor, { pageSize: getPageSize(context) }); + } +}); + + +function getPageSize(context: INotebookCellActionContext) { + const editor = context.notebookEditor; + const layoutInfo = editor._getViewModel().layoutInfo; + const lineHeight = layoutInfo?.fontInfo.lineHeight || 17; + return Math.max(1, Math.floor((layoutInfo?.height || 0) / lineHeight) - 2); +} + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ id: 'notebook', From faaeb6be4130ccd9d0e4379f6bde4ab919b23990 Mon Sep 17 00:00:00 2001 From: Sam Estep Date: Sun, 2 Jan 2022 23:11:16 -0500 Subject: [PATCH 0934/2210] Add option to not pass --no-ignore-parent to rg --- src/vs/workbench/api/common/extHostWorkspace.ts | 1 + .../contrib/search/browser/search.contribution.ts | 6 ++++++ .../workbench/contrib/search/browser/searchView.ts | 2 +- .../workbench/contrib/search/common/queryBuilder.ts | 2 ++ .../contrib/search/test/browser/queryBuilder.test.ts | 2 +- .../test/electron-browser/queryBuilder.test.ts | 2 +- .../services/search/common/fileSearchManager.ts | 1 + src/vs/workbench/services/search/common/search.ts | 2 ++ .../services/search/common/searchExtTypes.ts | 12 ++++++++++++ .../services/search/common/textSearchManager.ts | 1 + .../services/search/node/ripgrepFileSearch.ts | 2 +- .../services/search/node/ripgrepTextSearchEngine.ts | 4 +++- src/vscode-dts/vscode.proposed.findTextInFiles.d.ts | 6 ++++++ .../vscode.proposed.textSearchProvider.d.ts | 6 ++++++ 14 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 19a79681ef6a6..b499cde36ae26 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -480,6 +480,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, + disregardParentIgnoreFiles: typeof options.useParentIgnoreFiles === 'boolean' ? !options.useParentIgnoreFiles : undefined, disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : true, fileEncoding: options.encoding, maxResults: options.maxResults, diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 5390a2556d4cf..bd751ebb3a9c2 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -882,6 +882,12 @@ configurationRegistry.registerConfiguration({ default: false, scope: ConfigurationScope.RESOURCE }, + 'search.useParentIgnoreFiles': { + type: 'boolean', + markdownDescription: nls.localize('useParentIgnoreFiles', "Controls whether to use `.gitignore` and `.ignore` files in parent directories when searching for files. Requires `#search.useIgnoreFiles#` to be enabled."), + default: false, + scope: ConfigurationScope.RESOURCE + }, 'search.quickOpen.includeSymbols': { type: 'boolean', description: nls.localize('search.quickOpen.includeSymbols', "Whether to include results from a global symbol search in the file results for Quick Open."), diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 4efb971169ffc..8395d4b6c373f 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1574,7 +1574,7 @@ export class SearchView extends ViewPane { private onOpenSettings(e: dom.EventLike): void { dom.EventHelper.stop(e, false); - this.openSettings('@id:files.exclude,search.exclude,search.useGlobalIgnoreFiles,search.useIgnoreFiles'); + this.openSettings('@id:files.exclude,search.exclude,search.useParentIgnoreFiles,search.useGlobalIgnoreFiles,search.useIgnoreFiles'); } private openSettings(query: string): Promise { diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index f594a54b2ed90..c8f946a4e8148 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -59,6 +59,7 @@ export interface ICommonQueryBuilderOptions { maxFileSize?: number; disregardIgnoreFiles?: boolean; disregardGlobalIgnoreFiles?: boolean; + disregardParentIgnoreFiles?: boolean; disregardExcludeSettings?: boolean; disregardSearchExcludeSettings?: boolean; ignoreSymlinks?: boolean; @@ -507,6 +508,7 @@ export class QueryBuilder { fileEncoding: folderConfig.files && folderConfig.files.encoding, disregardIgnoreFiles: typeof options.disregardIgnoreFiles === 'boolean' ? options.disregardIgnoreFiles : !folderConfig.search.useIgnoreFiles, disregardGlobalIgnoreFiles: typeof options.disregardGlobalIgnoreFiles === 'boolean' ? options.disregardGlobalIgnoreFiles : !folderConfig.search.useGlobalIgnoreFiles, + disregardParentIgnoreFiles: typeof options.disregardParentIgnoreFiles === 'boolean' ? options.disregardParentIgnoreFiles : !folderConfig.search.useParentIgnoreFiles, ignoreSymlinks: typeof options.ignoreSymlinks === 'boolean' ? options.ignoreSymlinks : !folderConfig.search.followSymlinks, }; } diff --git a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts index 12160bb821c78..e4be7d216ddda 100644 --- a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts @@ -22,7 +22,7 @@ import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; const DEFAULT_EDITOR_CONFIG = {}; -const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; +const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true, useParentIgnoreFiles: true }; const DEFAULT_QUERY_PROPS = {}; const DEFAULT_TEXT_QUERY_PROPS = { usePCRE2: false }; diff --git a/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts index e6a021cc71140..590cfd98a3057 100644 --- a/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts @@ -15,7 +15,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; const DEFAULT_EDITOR_CONFIG = {}; -const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; +const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true, useParentIgnoreFiles: true }; suite('QueryBuilder', () => { const ROOT_1 = fixPath('/foo/root1'); diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index e33942165b876..0b39f4dd684e7 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -172,6 +172,7 @@ class FileSearchEngine { includes, useIgnoreFiles: !fq.disregardIgnoreFiles, useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, + useParentIgnoreFiles: !fq.disregardParentIgnoreFiles, followSymlinks: !fq.ignoreSymlinks, maxResults: this.config.maxResults, session: this.sessionToken diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 9f551e31421df..ae52a017c2693 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -69,6 +69,7 @@ export interface IFolderQuery { fileEncoding?: string; disregardIgnoreFiles?: boolean; disregardGlobalIgnoreFiles?: boolean; + disregardParentIgnoreFiles?: boolean; ignoreSymlinks?: boolean; } @@ -364,6 +365,7 @@ export interface ISearchConfigurationProperties { */ useIgnoreFiles: boolean; useGlobalIgnoreFiles: boolean; + useParentIgnoreFiles: boolean; followSymlinks: boolean; smartCase: boolean; globalFindClipboard: boolean; diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 3c01bb86948a7..4a8a80a298240 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -161,6 +161,12 @@ export interface SearchOptions { * See the vscode setting `"search.useGlobalIgnoreFiles"`. */ useGlobalIgnoreFiles: boolean; + + /** + * Whether files in parent directories that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useParentIgnoreFiles"`. + */ + useParentIgnoreFiles: boolean; } /** @@ -419,6 +425,12 @@ export interface FindTextInFilesOptions { */ useGlobalIgnoreFiles?: boolean; + /** + * Whether files in parent directories that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useParentIgnoreFiles"`. + */ + useParentIgnoreFiles: boolean; + /** * Whether symlinks should be followed while searching. * See the vscode setting `"search.followSymlinks"`. diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts index de7d548702723..1f1747a6805c3 100644 --- a/src/vs/workbench/services/search/common/textSearchManager.ts +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -189,6 +189,7 @@ export class TextSearchManager { includes, useIgnoreFiles: !fq.disregardIgnoreFiles, useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, + useParentIgnoreFiles: !fq.disregardParentIgnoreFiles, followSymlinks: !fq.ignoreSymlinks, encoding: fq.fileEncoding && this.fileUtils.toCanonicalName(fq.fileEncoding), maxFileSize: this.query.maxFileSize, diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index d74392713750d..90f707b2f7601 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -58,7 +58,7 @@ function getRgArgs(config: IFileQuery, folderQuery: IFolderQuery, includePattern if (folderQuery.disregardIgnoreFiles !== false) { // Don't use .gitignore or .ignore args.push('--no-ignore'); - } else { + } else if (folderQuery.disregardParentIgnoreFiles !== false) { args.push('--no-ignore-parent'); } diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 029221e66d83c..187255946cb86 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -403,7 +403,9 @@ function getRgArgs(query: TextSearchQuery, options: TextSearchOptions): string[] } if (options.useIgnoreFiles) { - args.push('--no-ignore-parent'); + if (!options.useParentIgnoreFiles) { + args.push('--no-ignore-parent'); + } } else { // Don't use .gitignore or .ignore args.push('--no-ignore'); diff --git a/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts b/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts index 14cd44d20485b..25e3ae0a39f84 100644 --- a/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts +++ b/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts @@ -47,6 +47,12 @@ declare module 'vscode' { */ useGlobalIgnoreFiles?: boolean; + /** + * Whether files in parent directories that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useParentIgnoreFiles"`. + */ + useParentIgnoreFiles?: boolean; + /** * Whether symlinks should be followed while searching. * See the vscode setting `"search.followSymlinks"`. diff --git a/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts b/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts index 9eba555d007bf..a65cc59a66694 100644 --- a/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts @@ -80,6 +80,12 @@ declare module 'vscode' { * See the vscode setting `"search.useGlobalIgnoreFiles"`. */ useGlobalIgnoreFiles: boolean; + + /** + * Whether files in parent directories that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useParentIgnoreFiles"`. + */ + useParentIgnoreFiles: boolean; } /** From 84ef964f73ff0bc487330573cc4a775525c61bf1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Jan 2022 10:18:42 +0100 Subject: [PATCH 0935/2210] enable typescript inlay hints for all for feedback --- .vscode/settings.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d03741307f0c9..a896b7e5a7bee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,7 +42,9 @@ } ], "eslint.options": { - "rulePaths": ["./build/lib/eslint"] + "rulePaths": [ + "./build/lib/eslint" + ] }, "typescript.tsdk": "node_modules/typescript/lib", "npm.exclude": "**/extensions/**", @@ -52,11 +54,15 @@ "typescript.preferences.quoteStyle": "single", "json.schemas": [ { - "fileMatch": ["cgmanifest.json"], + "fileMatch": [ + "cgmanifest.json" + ], "url": "./.vscode/cgmanifest.schema.json" }, { - "fileMatch": ["cglicenses.json"], + "fileMatch": [ + "cglicenses.json" + ], "url": "./.vscode/cglicenses.schema.json" } ], @@ -78,6 +84,12 @@ "editor.formatOnSave": true }, "typescript.tsc.autoDetect": "off", + "editor.inlayHints.enabled": true, + "typescript.inlayHints.enumMemberValues.enabled": true, + "typescript.inlayHints.functionLikeReturnTypes.enabled": true, + "typescript.inlayHints.parameterTypes.enabled": true, + "typescript.inlayHints.propertyDeclarationTypes.enabled": true, + "typescript.inlayHints.variableTypes.enabled": true, "testing.autoRun.mode": "rerun", "conventionalCommits.scopes": [ "tree", From 8ee675dc5548a9619b89e9eb1ece1aef687748cd Mon Sep 17 00:00:00 2001 From: Philipp Wagner Date: Mon, 3 Jan 2022 10:47:30 +0100 Subject: [PATCH 0936/2210] Update ripgrep binaries to include ppc64le and s390x ripgrep is a binary dependency, which gets pulled in through vscode-ripgrep, which itself downloads binaries from https://github.com/microsoft/ripgrep-prebuilt. The v13.0.0-2 of ripgrep-prebuilt (and only this version) didn't include binaries for s390x and ppc due to a change in CI infrastructure. The newer version v13.0.0-4 includes those binaries again (and has no other user-visible changes, see https://github.com/microsoft/ripgrep-prebuilt/releases). The previously used version of vscode-ripgrep pulled in v13.0.0-2 of the prebuilt binaries. The newer version 1.13.2 pulls in v13.0.0-4 of the binaries. Again, this version bump only includes the changes to the binaries and nothing else (see also https://github.com/microsoft/vscode-ripgrep/compare/v1.12.1...v1.13.2 The version numbers are slightly misleading here.) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a47082a175b0b..ae8c1ba892e08 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "vscode-oniguruma": "1.6.1", "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", - "vscode-ripgrep": "^1.12.1", + "vscode-ripgrep": "^1.13.2", "vscode-textmate": "6.0.0", "xterm": "4.16.0", "xterm-addon-search": "0.9.0-beta.6", diff --git a/yarn.lock b/yarn.lock index 6c5cb2e2a1b2a..d48913f1b1eac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10426,10 +10426,10 @@ vscode-regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-ripgrep@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.12.1.tgz#4a319809d4010ea230659ce605fddacd1e36a589" - integrity sha512-4edKlcXNSKdC9mIQmQ9Wl25v0SF5DOK31JlvKHKHYV4co0V2MjI9pbDPdmogwbtiykz+kFV/cKnZH2TgssEasQ== +vscode-ripgrep@^1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.13.2.tgz#8ccebc33f14d54442c4b11962aead163c55b506e" + integrity sha512-RlK9U87EokgHfiOjDQ38ipQQX936gWOcWPQaJpYf+kAkz1PQ1pK2n7nhiscdOmLu6XGjTs7pWFJ/ckonpN7twQ== dependencies: https-proxy-agent "^4.0.0" proxy-from-env "^1.1.0" From b5d3ec5b49554bea1d7e1bc372ac8705595b98c4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Jan 2022 11:34:27 +0100 Subject: [PATCH 0937/2210] fix https://github.com/microsoft/vscode/issues/139910 --- src/vs/base/common/lifecycle.ts | 7 ++++ src/vs/editor/contrib/codelens/codelens.ts | 4 +++ .../contrib/codelens/codelensController.ts | 34 +++++++++++-------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 0cc516977c8f1..0ffa00ff72483 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -199,6 +199,13 @@ export class DisposableStore implements IDisposable { this.clear(); } + /** + * Returns `true` if this object has been disposed + */ + public get isDisposed(): boolean { + return this._isDisposed; + } + /** * Dispose of all registered disposables but do not mark this object as disposed. */ diff --git a/src/vs/editor/contrib/codelens/codelens.ts b/src/vs/editor/contrib/codelens/codelens.ts index 6463b1adf88bc..0bf2566db241d 100644 --- a/src/vs/editor/contrib/codelens/codelens.ts +++ b/src/vs/editor/contrib/codelens/codelens.ts @@ -28,6 +28,10 @@ export class CodeLensModel { this._disposables.dispose(); } + get isDisposed(): boolean { + return this._disposables.isDisposed; + } + add(list: CodeLensList, provider: CodeLensProvider): void { this._disposables.add(list); for (const symbol of list.lenses) { diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index e25ee3c591a70..21335bc779121 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -439,8 +439,8 @@ export class CodeLensContribution implements IEditorContribution { }); } - getLenses(): readonly CodeLensWidget[] { - return this._lenses; + getModel(): CodeLensModel | undefined { + return this._currentCodeLensModel; } } @@ -472,19 +472,20 @@ registerEditorAction(class ShowLensesInCurrentLine extends EditorAction { if (!codelensController) { return; } - const items: { label: string, command: Command }[] = []; - for (let lens of codelensController.getLenses()) { - if (lens.getLineNumber() === lineNumber) { - for (let item of lens.getItems()) { - const { command } = item.symbol; - if (command) { - items.push({ - label: command.title, - command: command - }); - } - } + const model = codelensController.getModel(); + if (!model) { + // nothing + return; + } + + const items: { label: string, command: Command }[] = []; + for (const lens of model.lenses) { + if (lens.symbol.command && lens.symbol.range.startLineNumber === lineNumber) { + items.push({ + label: lens.symbol.command.title, + command: lens.symbol.command + }); } } @@ -499,6 +500,11 @@ registerEditorAction(class ShowLensesInCurrentLine extends EditorAction { return; } + if (model.isDisposed) { + // retry whenever the model has been disposed + return await commandService.executeCommand(this.id); + } + try { await commandService.executeCommand(item.command.id, ...(item.command.arguments || [])); } catch (err) { From 3441d36d84e527502f0cb64482c2274012057814 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Jan 2022 12:34:10 +0100 Subject: [PATCH 0938/2210] add JSON schema for `extensionEnabledApiProposals`, fyi @aeschli --- extensions/configuration-editing/package.json | 8 ++++-- .../platform/product/common/productService.ts | 3 +++ .../extensions/common/extensionsRegistry.ts | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index aabe3c00212d4..a7b71cd05c84f 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -51,9 +51,9 @@ ".devcontainer.json" ], "filenamePatterns": [ - "**/.devcontainer/devcontainer.json", + "**/.devcontainer/devcontainer.json", "**/User/snippets/*.json" - ] + ] } ], "jsonValidation": [ @@ -136,6 +136,10 @@ { "fileMatch": "%APP_SETTINGS_HOME%/globalStorage/ms-vscode-remote.remote-containers/imageConfigs/*.json", "url": "./schemas/attachContainer.schema.json" + }, + { + "fileMatch": "**/quality/*/product.json", + "url": "vscode://schemas/vscode-product" } ] }, diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index fcb9108cb9c50..c819c5caa1d26 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -13,3 +13,6 @@ export interface IProductService extends Readonly { readonly _serviceBrand: undefined; } + + +export const productSchemaId = 'vscode://schemas/vscode-product'; diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 7804f575dc3ff..7ade0916dd0a4 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -14,6 +14,7 @@ import { IMessage } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription, EXTENSION_CATEGORIES, ExtensionKind } from 'vs/platform/extensions/common/extensions'; import { allApiProposals } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; import { values } from 'vs/base/common/collections'; +import { productSchemaId } from 'vs/platform/product/common/productService'; const schemaRegistry = Registry.as(Extensions.JSONContribution); @@ -566,3 +567,29 @@ Registry.add(PRExtensions.ExtensionsRegistry, new ExtensionsRegistryImpl()); export const ExtensionsRegistry: ExtensionsRegistryImpl = Registry.as(PRExtensions.ExtensionsRegistry); schemaRegistry.registerSchema(schemaId, schema); + + +schemaRegistry.registerSchema(productSchemaId, { + properties: { + extensionAllowedProposedApi: { + type: 'array', + deprecationMessage: nls.localize('product.extensionAllowedProposedApi', "Use `extensionEnabledApiProposals` instead.") + }, + extensionEnabledApiProposals: { + description: nls.localize('product.extensionEnabledApiProposals', "API proposals that the respective extensions can freely use."), + type: 'object', + properties: {}, + additionalProperties: { + anyOf: [{ + type: 'array', + uniqueItems: true, + items: { + type: 'string', + enum: Object.keys(allApiProposals), + markdownEnumDescriptions: values(allApiProposals) + } + }] + } + } + } +}); From cddc7dde413ca62a03bc99bcf41d7054cf11b9bd Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 3 Jan 2022 14:48:15 +0100 Subject: [PATCH 0939/2210] Fix #139803 --- src/vs/workbench/contrib/scm/browser/scm.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index d98aaa972f434..9509b4638e862 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -63,12 +63,12 @@ viewsRegistry.registerViewWelcomeContent(VIEW_PANE_ID, { viewsRegistry.registerViewWelcomeContent(VIEW_PANE_ID, { content: localize('no open repo in an untrusted workspace', "None of the registered source control providers work in Restricted Mode."), - when: ContextKeyExpr.and(WorkspaceTrustContext.IsEnabled, WorkspaceTrustContext.IsTrusted.toNegated()) + when: ContextKeyExpr.and(ContextKeyExpr.has('scm.providerCount'), ContextKeyExpr.equals('scm.providerCount', 0), WorkspaceTrustContext.IsEnabled, WorkspaceTrustContext.IsTrusted.toNegated()) }); viewsRegistry.registerViewWelcomeContent(VIEW_PANE_ID, { content: `[${localize('manageWorkspaceTrustAction', "Manage Workspace Trust")}](command:${MANAGE_TRUST_COMMAND_ID})`, - when: ContextKeyExpr.and(WorkspaceTrustContext.IsEnabled, WorkspaceTrustContext.IsTrusted.toNegated()) + when: ContextKeyExpr.and(ContextKeyExpr.has('scm.providerCount'), ContextKeyExpr.equals('scm.providerCount', 0), WorkspaceTrustContext.IsEnabled, WorkspaceTrustContext.IsTrusted.toNegated()) }); viewsRegistry.registerViews([{ From 4d21a08ca3b933f5c8168fc3e6fb99d722e1d02d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Jan 2022 16:54:26 +0100 Subject: [PATCH 0940/2210] :lipstick: --- .../extensions/electron-browser/extensionsAutoProfiler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts index 0320b2f08b7f7..e7a5552d8e8b1 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts @@ -143,7 +143,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont // print message to log const path = joinPath(this._environmentServie.tmpDir, `exthost-${Math.random().toString(16).slice(2, 8)}.cpuprofile`); await this._fileService.writeFile(path, VSBuffer.fromString(JSON.stringify(profile.data))); - this._logService.warn(`UNRESPONSIVE extension host, '${top.id}' took ${top!.percentage}% of ${duration / 1e3}ms, saved PROFILE here: '${path}'`, data); + this._logService.warn(`UNRESPONSIVE extension host, '${top.id}' took ${top.percentage}% of ${duration / 1e3}ms, saved PROFILE here: '${path}'`, data); /* __GDPR__ From be6b343b6b49410c0492c33accc1ef1aefd1f59f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Jan 2022 16:56:09 +0100 Subject: [PATCH 0941/2210] move `v8-inspect-profiler` to shared process and wrap inside `IV8InspectProfilingService`, https://github.com/microsoft/vscode/issues/111211 --- .../sharedProcess/sharedProcessMain.ts | 9 +++ src/vs/platform/profiling/common/profiling.ts | 58 +++++++++++++++++++ .../electron-sandbox/profilingService.ts | 9 +++ .../profiling/node/profilingService.ts | 33 +++++++++++ .../electron-sandbox/extensionsSlowActions.ts | 29 +++++----- .../runtimeExtensionsEditor.ts | 5 +- .../services/extensions/common/extensions.ts | 3 +- .../electron-browser/extensionHostProfiler.ts | 28 +++++---- src/vs/workbench/workbench.sandbox.main.ts | 1 + 9 files changed, 147 insertions(+), 28 deletions(-) create mode 100644 src/vs/platform/profiling/common/profiling.ts create mode 100644 src/vs/platform/profiling/electron-sandbox/profilingService.ts create mode 100644 src/vs/platform/profiling/node/profilingService.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 3aa576113d5cd..1b9fededb4805 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -97,6 +97,8 @@ import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentitySe import { isLinux } from 'vs/base/common/platform'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { DiskFileSystemProviderClient, LOCAL_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/files/common/diskFileSystemProviderClient'; +import { InspectProfilingService as V8InspectProfilingService } from 'vs/platform/profiling/node/profilingService'; +import { IV8InspectProfilingService } from 'vs/platform/profiling/common/profiling'; class SharedProcessMain extends Disposable { @@ -242,6 +244,9 @@ class SharedProcessMain extends Disposable { // Checksum services.set(IChecksumService, new SyncDescriptor(ChecksumService)); + // V8 Inspect profiler + services.set(IV8InspectProfilingService, new SyncDescriptor(V8InspectProfilingService)); + // Native Host const nativeHostService = ProxyChannel.toService(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId }); services.set(INativeHostService, nativeHostService); @@ -376,6 +381,10 @@ class SharedProcessMain extends Disposable { const checksumChannel = ProxyChannel.fromService(accessor.get(IChecksumService)); this.server.registerChannel('checksum', checksumChannel); + // Profiling + const profilingChannel = ProxyChannel.fromService(accessor.get(IV8InspectProfilingService)); + this.server.registerChannel('v8InspectProfiling', profilingChannel); + // Settings Sync const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(accessor.get(IUserDataSyncMachinesService)); this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel); diff --git a/src/vs/platform/profiling/common/profiling.ts b/src/vs/platform/profiling/common/profiling.ts new file mode 100644 index 0000000000000..ddd6d1a294915 --- /dev/null +++ b/src/vs/platform/profiling/common/profiling.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { basename, isAbsolute, join } from 'vs/base/common/path'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IV8Profile { + nodes: IV8ProfileNode[]; + samples?: number[]; + timeDeltas?: number[]; + startTime: number; + endTime: number; +} + +export interface IV8ProfileNode { + id: number; + hitCount?: number; + children?: number[]; + callFrame: IV8CallFrame; + deoptReason?: string; + positionTicks?: { line: number; ticks: number }[]; +} + +export interface IV8CallFrame { + url: string; + scriptId: string; + functionName: string; + lineNumber: number; + columnNumber: number; +} + +export const IV8InspectProfilingService = createDecorator('IV8InspectProfilingService'); + +export interface IV8InspectProfilingService { + + _serviceBrand: undefined; + + startProfiling(options: { port: number }): Promise + + stopProfiling(sessionId: string): Promise +} + + +export namespace Utils { + + export function rewriteAbsolutePaths(profile: IV8Profile, replace: string = 'noAbsolutePaths') { + for (const node of profile.nodes) { + if (node.callFrame && node.callFrame.url) { + if (isAbsolute(node.callFrame.url)) { + node.callFrame.url = join(replace, basename(node.callFrame.url)); + } + } + } + return profile; + } +} diff --git a/src/vs/platform/profiling/electron-sandbox/profilingService.ts b/src/vs/platform/profiling/electron-sandbox/profilingService.ts new file mode 100644 index 0000000000000..898610cdd9543 --- /dev/null +++ b/src/vs/platform/profiling/electron-sandbox/profilingService.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; +import { IV8InspectProfilingService } from 'vs/platform/profiling/common/profiling'; + +registerSharedProcessRemoteService(IV8InspectProfilingService, 'v8InspectProfiling', { supportsDelayedInstantiation: true }); diff --git a/src/vs/platform/profiling/node/profilingService.ts b/src/vs/platform/profiling/node/profilingService.ts new file mode 100644 index 0000000000000..ab207607d0a83 --- /dev/null +++ b/src/vs/platform/profiling/node/profilingService.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { ProfilingSession } from 'v8-inspect-profiler'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IV8InspectProfilingService, IV8Profile } from 'vs/platform/profiling/common/profiling'; + +export class InspectProfilingService implements IV8InspectProfilingService { + + _serviceBrand: undefined; + + private readonly _sessions = new Map(); + + async startProfiling(options: { port: number; }): Promise { + const prof = await import('v8-inspect-profiler'); + const session = await prof.startProfiling({ port: options.port, checkForPaused: true }); + const id = generateUuid(); + this._sessions.set(id, session); + return id; + } + + async stopProfiling(sessionId: string): Promise { + const session = this._sessions.get(sessionId); + if (!session) { + throw new Error(`UNKNOWN session '${sessionId}'`); + } + const result = await session.stop(); + this._sessions.delete(sessionId); + return result.profile; + } +} diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts index 0c5855b4e3b59..ece72ff038c73 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts @@ -13,12 +13,14 @@ import { localize } from 'vs/nls'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IRequestService, asText } from 'vs/platform/request/common/request'; import { joinPath } from 'vs/base/common/resources'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { Utils } from 'vs/platform/profiling/common/profiling'; +import { IFileService } from 'vs/platform/files/common/files'; +import { VSBuffer } from 'vs/base/common/buffer'; abstract class RepoInfo { abstract get base(): string; @@ -122,7 +124,8 @@ class ReportExtensionSlowAction extends Action { @IOpenerService private readonly _openerService: IOpenerService, @IProductService private readonly _productService: IProductService, @INativeHostService private readonly _nativeHostService: INativeHostService, - @INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService + @INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, + @IFileService private readonly _fileService: IFileService, ) { super('report.slow', localize('cmd.report', "Report Issue")); } @@ -130,10 +133,9 @@ class ReportExtensionSlowAction extends Action { override async run(): Promise { // rewrite pii (paths) and store on disk - const profiler = await import('v8-inspect-profiler'); - const data = profiler.rewriteAbsolutePaths({ profile: this.profile.data }, 'pii_removed'); - const path = joinPath(this._environmentService.tmpDir, `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`).fsPath; - await profiler.writeProfile(data, path).then(undefined, onUnexpectedError); + const data = Utils.rewriteAbsolutePaths(this.profile.data, 'pii_removed'); + const path = joinPath(this._environmentService.tmpDir, `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`); + await this._fileService.writeFile(path, VSBuffer.fromString(JSON.stringify(data, undefined, 4))); // build issue const os = await this._nativeHostService.getOSProperties(); @@ -153,7 +155,7 @@ class ReportExtensionSlowAction extends Action { Severity.Info, localize('attach.title', "Did you attach the CPU-Profile?"), undefined, - { detail: localize('attach.msg', "This is a reminder to make sure that you have not forgotten to attach '{0}' to the issue you have just created.", path) } + { detail: localize('attach.msg', "This is a reminder to make sure that you have not forgotten to attach '{0}' to the issue you have just created.", path.fsPath) } ); } } @@ -166,7 +168,9 @@ class ShowExtensionSlowAction extends Action { readonly profile: IExtensionHostProfile, @IDialogService private readonly _dialogService: IDialogService, @IOpenerService private readonly _openerService: IOpenerService, - @INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService + @INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, + @IFileService private readonly _fileService: IFileService, + ) { super('show.slow', localize('cmd.show', "Show Issues")); } @@ -174,10 +178,9 @@ class ShowExtensionSlowAction extends Action { override async run(): Promise { // rewrite pii (paths) and store on disk - const profiler = await import('v8-inspect-profiler'); - const data = profiler.rewriteAbsolutePaths({ profile: this.profile.data }, 'pii_removed'); - const path = joinPath(this._environmentService.tmpDir, `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`).fsPath; - await profiler.writeProfile(data, path).then(undefined, onUnexpectedError); + const data = Utils.rewriteAbsolutePaths(this.profile.data, 'pii_removed'); + const path = joinPath(this._environmentService.tmpDir, `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`); + await this._fileService.writeFile(path, VSBuffer.fromString(JSON.stringify(data, undefined, 4))); // show issues const url = `${this.repoInfo.base}/${this.repoInfo.owner}/${this.repoInfo.repo}/issues?utf8=✓&q=is%3Aissue+state%3Aopen+%22Extension+causes+high+cpu+load%22`; @@ -187,7 +190,7 @@ class ShowExtensionSlowAction extends Action { Severity.Info, localize('attach.title', "Did you attach the CPU-Profile?"), undefined, - { detail: localize('attach.msg2', "This is a reminder to make sure that you have not forgotten to attach '{0}' to an existing performance issue.", path) } + { detail: localize('attach.msg2', "This is a reminder to make sure that you have not forgotten to attach '{0}' to an existing performance issue.", path.fsPath) } ); } } diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts index ea3f8ed3b89e2..ca31c6145d1ca 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts @@ -25,6 +25,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IV8Profile, Utils } from 'vs/platform/profiling/common/profiling'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -208,13 +209,11 @@ export class SaveExtensionHostProfileAction extends Action { let savePath = picked.filePath; if (this._environmentService.isBuilt) { - const profiler = await import('v8-inspect-profiler'); // when running from a not-development-build we remove // absolute filenames because we don't want to reveal anything // about users. We also append the `.txt` suffix to make it // easier to attach these files to GH issues - let tmp = profiler.rewriteAbsolutePaths({ profile: dataToWrite as any }, 'piiRemoved'); - dataToWrite = tmp.profile; + dataToWrite = Utils.rewriteAbsolutePaths(dataToWrite as IV8Profile, 'piiRemoved'); savePath = savePath + '.txt'; } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 8f9b89d41b9aa..570b26b5fef30 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -13,6 +13,7 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ApiProposalName } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; +import { IV8Profile } from 'vs/platform/profiling/common/profiling'; export const nullExtensionDescription = Object.freeze({ identifier: new ExtensionIdentifier('nullExtensionDescription'), @@ -99,7 +100,7 @@ export interface IExtensionHostProfile { /** * Get the information as a .cpuprofile. */ - data: object; + data: IV8Profile; /** * Get the aggregated time per segmentId diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts index 342ec2dc4021a..107d35a37cea9 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Profile, ProfileNode } from 'v8-inspect-profiler'; import { TernarySearchTree } from 'vs/base/common/map'; import { realpathSync } from 'vs/base/node/extpath'; import { IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; @@ -11,25 +10,32 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { withNullAsUndefined } from 'vs/base/common/types'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { IV8InspectProfilingService, IV8Profile, IV8ProfileNode } from 'vs/platform/profiling/common/profiling'; +import { once } from 'vs/base/common/functional'; export class ExtensionHostProfiler { - constructor(private readonly _port: number, @IExtensionService private readonly _extensionService: IExtensionService) { + constructor( + private readonly _port: number, + @IExtensionService private readonly _extensionService: IExtensionService, + @IV8InspectProfilingService private readonly _profilingService: IV8InspectProfilingService, + ) { } public async start(): Promise { - const profiler = await import('v8-inspect-profiler'); - const session = await profiler.startProfiling({ port: this._port, checkForPaused: true }); + + const id = await this._profilingService.startProfiling({ port: this._port }); + return { - stop: async () => { - const profile = await session.stop(); + stop: once(async () => { + const profile = await this._profilingService.stopProfiling(id); const extensions = await this._extensionService.getExtensions(); - return this.distill((profile as any).profile, extensions); - } + return this._distill(profile, extensions); + }) }; } - private distill(profile: Profile, extensions: IExtensionDescription[]): IExtensionHostProfile { + private _distill(profile: IV8Profile, extensions: IExtensionDescription[]): IExtensionHostProfile { let searchTree = TernarySearchTree.forUris(); for (let extension of extensions) { if (extension.extensionLocation.scheme === Schemas.file) { @@ -38,13 +44,13 @@ export class ExtensionHostProfiler { } let nodes = profile.nodes; - let idsToNodes = new Map(); + let idsToNodes = new Map(); let idsToSegmentId = new Map(); for (let node of nodes) { idsToNodes.set(node.id, node); } - function visit(node: ProfileNode, segmentId: ProfileSegmentId | null) { + function visit(node: IV8ProfileNode, segmentId: ProfileSegmentId | null) { if (!segmentId) { switch (node.callFrame.functionName) { case '(root)': diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index e9aabd04631f8..f3e2f530aaeca 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -75,6 +75,7 @@ import 'vs/platform/remote/electron-sandbox/sharedProcessTunnelService'; import 'vs/workbench/services/remote/electron-sandbox/tunnelService'; import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService'; import 'vs/platform/checksum/electron-sandbox/checksumService'; +import 'vs/platform/profiling/electron-sandbox/profilingService'; import 'vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService'; import 'vs/workbench/services/files/electron-sandbox/elevatedFileService'; import 'vs/workbench/services/search/electron-sandbox/searchService'; From 5d6872f1548d0074e998d5bbae26313d4f51061c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 3 Jan 2022 17:34:55 +0100 Subject: [PATCH 0942/2210] User data provider should not iterate over raw changes (fix #126660) (#139793) --- .../sharedProcess/sharedProcessMain.ts | 1 + src/vs/platform/files/common/files.ts | 8 +++++ .../userData/common/fileUserDataProvider.ts | 31 ++++++++++++------- .../test/browser/fileUserDataProvider.test.ts | 20 ++++++++++-- .../electron-sandbox/desktop.main.ts | 2 +- .../configurationEditingService.test.ts | 2 +- .../test/browser/configurationService.test.ts | 14 ++++----- .../test/browser/keybindingEditing.test.ts | 2 +- .../workingCopyBackupService.test.ts | 2 +- 9 files changed, 57 insertions(+), 25 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 1b9fededb4805..5a8ac34d5d683 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -216,6 +216,7 @@ class SharedProcessMain extends Disposable { // processes, we want a single process handling these operations. this._register(new DiskFileSystemProviderClient(mainProcessService.getChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME), { pathCaseSensitive: isLinux })), Schemas.userData, + fileService, logService )); fileService.registerProvider(Schemas.userData, userDataFileSystemProvider); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 979ae34e920fc..76d618dbda73a 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -839,6 +839,14 @@ export class FileChangesEvent { */ get rawAdded(): TernarySearchTree | undefined { return this.added; } + /** + * @deprecated use the `contains` or `affects` method to efficiently find + * out if the event relates to a given resource. these methods ensure: + * - that there is no expensive lookup needed (by using a `TernarySearchTree`) + * - correctly handles `FileChangeType.DELETED` events + */ + get rawUpdated(): TernarySearchTree | undefined { return this.updated; } + /** * @deprecated use the `contains` or `affects` method to efficiently find * out if the event relates to a given resource. these methods ensure: diff --git a/src/vs/platform/userData/common/fileUserDataProvider.ts b/src/vs/platform/userData/common/fileUserDataProvider.ts index 211f88f7da62f..28ecb41ef6db9 100644 --- a/src/vs/platform/userData/common/fileUserDataProvider.ts +++ b/src/vs/platform/userData/common/fileUserDataProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, IFileSystemProviderWithFileAtomicReadCapability } from 'vs/platform/files/common/files'; +import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream'; @@ -34,10 +34,11 @@ export class FileUserDataProvider extends Disposable implements private readonly fileSystemScheme: string, private readonly fileSystemProvider: IFileSystemProviderWithFileReadWriteCapability & (IFileSystemProviderWithFileReadStreamCapability | IFileSystemProviderWithFileAtomicReadCapability), private readonly userDataScheme: string, - private readonly logService: ILogService, + fileService: IFileService, + private readonly logService: ILogService ) { super(); - this._register(this.fileSystemProvider.onDidChangeFile(e => this.handleFileChanges(e))); + this._register(fileService.onDidFilesChange(e => this.handleFileChanges(e))); } watch(resource: URI, opts: IWatchOptions): IDisposable { @@ -91,15 +92,23 @@ export class FileUserDataProvider extends Disposable implements return this.fileSystemProvider.delete(this.toFileSystemResource(resource), opts); } - private handleFileChanges(changes: readonly IFileChange[]): void { + private handleFileChanges(e: FileChangesEvent): void { const userDataChanges: IFileChange[] = []; - for (const change of changes) { - const userDataResource = this.toUserDataResource(change.resource); - if (this.watchResources.findSubstr(userDataResource)) { - userDataChanges.push({ - resource: userDataResource, - type: change.type - }); + for (const changes of [{ raw: e.rawAdded, type: FileChangeType.ADDED }, { raw: e.rawUpdated, type: FileChangeType.UPDATED }, { raw: e.rawDeleted, type: FileChangeType.DELETED }]) { + if (changes.raw) { + for (const [resource] of changes.raw) { + if (resource.scheme !== this.fileSystemScheme) { + continue; // only interested in file schemes + } + + const userDataResource = this.toUserDataResource(resource); + if (this.watchResources.findSubstr(userDataResource)) { + userDataChanges.push({ + resource: userDataResource, + type: changes.type + }); + } + } } } if (userDataChanges.length) { diff --git a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts index 775fd04c1d3bd..7802cc25d9eaf 100644 --- a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts +++ b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IFileService, FileChangeType, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, FileType, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { IFileService, FileChangeType, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, FileType, FileSystemProviderCapabilities, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; @@ -18,6 +18,7 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/common/product'; +import { isLinux } from 'vs/base/common/platform'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -51,7 +52,7 @@ suite('FileUserDataProvider', () => { environmentService = new TestEnvironmentService(userDataHomeOnDisk); - fileUserDataProvider = new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, logService); + fileUserDataProvider = new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new FileService(logService), logService); disposables.add(fileUserDataProvider); disposables.add(testObject.registerProvider(Schemas.userData, fileUserDataProvider)); }); @@ -299,6 +300,18 @@ class TestFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapa } +class TestFileService extends FileService { + + private readonly _onDidFilesChange2 = this._register(new Emitter()); + override readonly onDidFilesChange = this._onDidFilesChange2.event; + + constructor(fileEventEmitter: Emitter) { + super(new NullLogService()); + + fileEventEmitter.event(changes => this._onDidFilesChange2.fire(new FileChangesEvent(changes, !isLinux))); + } +} + suite('FileUserDataProvider - Watching', () => { let testObject: FileUserDataProvider; @@ -310,7 +323,8 @@ suite('FileUserDataProvider - Watching', () => { disposables.add(fileEventEmitter); setup(() => { - testObject = disposables.add(new FileUserDataProvider(rootFileResource.scheme, new TestFileSystemProvider(fileEventEmitter.event), Schemas.userData, new NullLogService())); + const logService = new NullLogService(); + testObject = disposables.add(new FileUserDataProvider(rootFileResource.scheme, new TestFileSystemProvider(fileEventEmitter.event), Schemas.userData, new TestFileService(fileEventEmitter), logService)); }); teardown(() => disposables.clear()); diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index c357ad3173b05..49ba7ee040f14 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -224,7 +224,7 @@ export class DesktopMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // User Data Provider - fileService.registerProvider(Schemas.userData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService))); + fileService.registerProvider(Schemas.userData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, fileService, logService))); // URI Identity const uriIdentityService = new UriIdentityService(fileService); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index de5fc1eee6285..423885e47d510 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -94,7 +94,7 @@ suite('ConfigurationEditingService', () => { environmentService = TestEnvironmentService; instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); - disposables.add(fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, logService)))); + disposables.add(fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index e52015cc9f444..81d11fc78bded 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -76,7 +76,7 @@ suite('WorkspaceContextService - Folder', () => { await fileService.createFolder(folder); const environmentService = TestEnvironmentService; - fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, new NullLogService()))); testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); await (testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -142,7 +142,7 @@ suite('WorkspaceContextService - Workspace', () => { const environmentService = TestEnvironmentService; const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, new NullLogService()))); testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, testObject); @@ -200,7 +200,7 @@ suite('WorkspaceContextService - Workspace Editing', () => { const environmentService = TestEnvironmentService; const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, new NullLogService()))); testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IFileService, fileService); @@ -443,7 +443,7 @@ suite('WorkspaceService - Initialization', () => { environmentService = TestEnvironmentService; const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, new NullLogService()))); testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -692,7 +692,7 @@ suite('WorkspaceConfigurationService - Folder', () => { environmentService = TestEnvironmentService; const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, new NullLogService()))); workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -1363,7 +1363,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { environmentService = TestEnvironmentService; const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, new NullLogService()))); const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IFileService, fileService); @@ -2023,7 +2023,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { environmentService = TestEnvironmentService; const remoteEnvironmentPromise = new Promise>(c => resolveRemoteEnvironment = () => c({ settingsPath: remoteSettingsResource })); const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); - fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, new NullLogService()))); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve(), needsCaching: () => false }; testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IWorkspaceContextService, testObject); diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts index ff9ab3eb841f0..c4dd189ceafb7 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts @@ -114,7 +114,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IThemeService, new TestThemeService()); instantiationService.stub(ILanguageConfigurationService, new TestLanguageConfigurationService()); instantiationService.stub(IModelService, disposables.add(instantiationService.createInstance(ModelService))); - fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); + fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, fileService, new NullLogService()))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService)); instantiationService.stub(IWorkingCopyFileService, disposables.add(instantiationService.createInstance(WorkingCopyFileService))); diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts index e380d4d82c331..e45fd933733ef 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts @@ -60,7 +60,7 @@ export class NodeTestWorkingCopyBackupService extends NativeWorkingCopyBackupSer this.diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, this.diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, this.diskFileSystemProvider, Schemas.userData, logService)); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, this.diskFileSystemProvider, Schemas.userData, fileService, logService)); this.fileService = fileService; this.backupResourceJoiners = []; From 0e8d52a1ff908ed4dc7df1f67ac2427cd375bbf0 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 3 Jan 2022 17:42:04 +0100 Subject: [PATCH 0943/2210] switch to icons from uris for language icons --- src/vs/editor/common/services/language.ts | 10 +- .../editor/common/services/languageService.ts | 5 +- .../common/services/languagesRegistry.ts | 7 +- .../common/extensionsApiProposals.ts | 1 + .../language/common/languageService.ts | 42 ++++++-- .../themes/browser/fileIconThemeData.ts | 102 +++++++++--------- .../themes/browser/workbenchThemeService.ts | 2 +- .../vscode.proposed.languageIcon.d.ts | 6 ++ 8 files changed, 100 insertions(+), 75 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.languageIcon.d.ts diff --git a/src/vs/editor/common/services/language.ts b/src/vs/editor/common/services/language.ts index 5ba22e26adecf..f18a8b2be9e9e 100644 --- a/src/vs/editor/common/services/language.ts +++ b/src/vs/editor/common/services/language.ts @@ -7,7 +7,6 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { ILanguageIdCodec } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export const ILanguageService = createDecorator('languageService'); @@ -23,7 +22,7 @@ export interface ILanguageExtensionPoint { /** * @internal */ - icon?: ThemeIcon; + icon?: ILanguageIcon; } export interface ILanguageSelection { @@ -36,6 +35,11 @@ export interface ILanguageNameIdPair { readonly languageId: string; } +export interface ILanguageIcon { + readonly light: URI; + readonly dark: URI; +} + export interface ILanguageService { readonly _serviceBrand: undefined; @@ -84,7 +88,7 @@ export interface ILanguageService { /** * Get the default icon for the language. */ - getIcon(languageId: string): ThemeIcon | null; + getIcon(languageId: string): ILanguageIcon | null; /** * Get all file extensions for a language. diff --git a/src/vs/editor/common/services/languageService.ts b/src/vs/editor/common/services/languageService.ts index ce50a3d7b30be..9906483bee79f 100644 --- a/src/vs/editor/common/services/languageService.ts +++ b/src/vs/editor/common/services/languageService.ts @@ -7,11 +7,10 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; -import { ILanguageNameIdPair, ILanguageSelection, ILanguageService } from 'vs/editor/common/services/language'; +import { ILanguageNameIdPair, ILanguageSelection, ILanguageService, ILanguageIcon } from 'vs/editor/common/services/language'; import { firstOrDefault } from 'vs/base/common/arrays'; import { ILanguageIdCodec, TokenizationRegistry } from 'vs/editor/common/languages'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export class LanguageService extends Disposable implements ILanguageService { public _serviceBrand: undefined; @@ -62,7 +61,7 @@ export class LanguageService extends Disposable implements ILanguageService { return this._registry.getMimeType(languageId); } - public getIcon(languageId: string): ThemeIcon | null { + public getIcon(languageId: string): ILanguageIcon | null { return this._registry.getIcon(languageId); } diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index 3dec4394e6bf5..d592ffe00993e 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -12,10 +12,9 @@ import { clearLanguageAssociations, getMimeTypes, registerLanguageAssociation } import { URI } from 'vs/base/common/uri'; import { ILanguageIdCodec, LanguageId } from 'vs/editor/common/languages'; import { ModesRegistry, PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; -import { ILanguageExtensionPoint, ILanguageNameIdPair } from 'vs/editor/common/services/language'; +import { ILanguageExtensionPoint, ILanguageNameIdPair, ILanguageIcon } from 'vs/editor/common/services/language'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; const hasOwnProperty = Object.prototype.hasOwnProperty; const NULL_LANGUAGE_ID = 'vs.editor.nullLanguage'; @@ -28,7 +27,7 @@ export interface IResolvedLanguage { extensions: string[]; filenames: string[]; configurationFiles: URI[]; - icons: ThemeIcon[]; + icons: ILanguageIcon[]; } export class LanguageIdCodec implements ILanguageIdCodec { @@ -323,7 +322,7 @@ export class LanguagesRegistry extends Disposable { return this._languages[languageId].filenames; } - public getIcon(languageId: string): ThemeIcon | null { + public getIcon(languageId: string): ILanguageIcon | null { if (!hasOwnProperty.call(this._languages, languageId)) { return null; } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0a1c1f9dcfb91..ccdd7e187e0b2 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -25,6 +25,7 @@ export const allApiProposals = Object.freeze({ fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', inlayHints: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlayHints.d.ts', inlineCompletions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts', + languageIcon: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageIcon.d.ts', languageStatus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatus.d.ts', notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', notebookConcatTextDocument: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookConcatTextDocument.d.ts', diff --git a/src/vs/workbench/services/language/common/languageService.ts b/src/vs/workbench/services/language/common/languageService.ts index 322b025b1fb89..730f59c3db7bb 100644 --- a/src/vs/workbench/services/language/common/languageService.ts +++ b/src/vs/workbench/services/language/common/languageService.ts @@ -12,10 +12,10 @@ import { LanguageService } from 'vs/editor/common/services/languageService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FILES_ASSOCIATIONS_CONFIG, IFilesConfiguration } from 'vs/platform/files/common/files'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export interface IRawLanguageExtensionPoint { id: string; @@ -26,7 +26,7 @@ export interface IRawLanguageExtensionPoint { aliases: string[]; mimetypes: string[]; configuration: string; - icon: string; + icon: { light: string; dark: string }; } export const languagesExtPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ @@ -88,8 +88,18 @@ export const languagesExtPoint: IExtensionPoint = default: './language-configuration.json' }, icon: { - description: nls.localize('vscode.extension.contributes.languages.icon', 'A contributed icon name to use as file icon, if no icon theme provides one for the language'), - type: 'string' + type: 'object', + description: localize('vscode.extension.contributes.languages.icon', 'A icon to use as file icon, if no icon theme provides one for the language.'), + properties: { + light: { + description: localize('vscode.extension.contributes.languages.icon.light', 'Icon path when a light theme is used'), + type: 'string' + }, + dark: { + description: localize('vscode.extension.contributes.languages.icon.dark', 'Icon path when a dark theme is used'), + type: 'string' + } + } } } } @@ -122,7 +132,7 @@ export class WorkbenchLanguageService extends LanguageService { for (let j = 0, lenJ = extension.value.length; j < lenJ; j++) { let ext = extension.value[j]; - if (isValidLanguageExtensionPoint(ext, extension.collector)) { + if (isValidLanguageExtensionPoint(ext, extension.description, extension.collector)) { let configuration: URI | undefined = undefined; if (ext.configuration) { configuration = joinPath(extension.description.extensionLocation, ext.configuration); @@ -136,7 +146,10 @@ export class WorkbenchLanguageService extends LanguageService { aliases: ext.aliases, mimetypes: ext.mimetypes, configuration: configuration, - icon: ThemeIcon.fromString(ext.icon) + icon: ext.icon && { + light: joinPath(extension.description.extensionLocation, ext.icon.light), + dark: joinPath(extension.description.extensionLocation, ext.icon.dark) + } }); } } @@ -191,7 +204,7 @@ function isUndefinedOrStringArray(value: string[]): boolean { return value.every(item => typeof item === 'string'); } -function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collector: ExtensionMessageCollector): boolean { +function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, extension: IExtensionDescription, collector: ExtensionMessageCollector): boolean { if (!value) { collector.error(localize('invalid.empty', "Empty value for `contributes.{0}`", languagesExtPoint.name)); return false; @@ -224,9 +237,16 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec collector.error(localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes')); return false; } - if (typeof value.icon !== 'undefined' && !ThemeIcon.fromString(value.icon)) { - collector.error(nls.localize('opt.icon', "property `{0}` can be omitted and must be of type `string`. It must in the form $([a-zA-Z0-9-]+)", 'icon')); - return false; + if (typeof value.icon !== 'undefined') { + const proposal = 'languageIcon'; + if (!isProposedApiEnabled(extension, proposal)) { + collector.error(`Extension '${extension.identifier.value}' CANNOT use API proposal: ${proposal}.\nIts package.json#enabledApiProposals-property declares: ${extension.enabledApiProposals?.join(', ') ?? '[]'} but NOT ${proposal}.\n The missing proposal MUST be added and you must start in extension development mode or use the following command line switch: --enable-proposed-api ${extension.identifier.value}`); + return false; + } + if (typeof value.icon !== 'object' || typeof value.icon.light !== 'string' || typeof value.icon.dark !== 'string') { + collector.error(localize('opt.icon', "property `{0}` can be omitted and must be of type `object` with properties `{1}` and `{2}` of type `string`", 'icon', 'light', 'dark')); + return false; + } } return true; } diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 5f5a03d82d2c6..7c22e8459a2cc 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -10,12 +10,10 @@ import * as resources from 'vs/base/common/resources'; import * as Json from 'vs/base/common/json'; import { ExtensionData, IThemeExtensionPoint, IWorkbenchFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; -import { asCSSPropertyValue, asCSSUrl } from 'vs/base/browser/dom'; +import { asCSSUrl } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { ILanguageService } from 'vs/editor/common/services/language'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; export class FileIconThemeData implements IWorkbenchFileIconTheme { @@ -106,9 +104,9 @@ export class FileIconThemeData implements IWorkbenchFileIconTheme { return undefined; } try { - let data = JSON.parse(input); + const data = JSON.parse(input); const theme = new FileIconThemeData('', '', null); - for (let key in data) { + for (const key in data) { switch (key) { case 'id': case 'label': @@ -194,8 +192,7 @@ export class FileIconThemeLoader { constructor( private readonly fileService: IExtensionResourceLoaderService, - private readonly modeService: ILanguageService, - private readonly themeService: IThemeService + private readonly modeService: ILanguageService ) { } @@ -216,8 +213,8 @@ export class FileIconThemeLoader { private loadIconThemeDocument(location: URI): Promise { return this.fileService.readExtensionResource(location).then((content) => { - let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content, errors); + const errors: Json.ParseError[] = []; + const contentValue = Json.parse(content, errors); if (errors.length > 0) { return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); } else if (Json.getNodeType(contentValue) !== 'object') { @@ -231,6 +228,8 @@ export class FileIconThemeLoader { const result = { content: '', hasFileIcons: false, hasFolderIcons: false, hidesExplorerArrows: !!iconThemeDocument.hidesExplorerArrows }; + let hasSpecificFileIcons = false; + if (!iconThemeDocument.iconDefinitions) { return result; } @@ -271,8 +270,8 @@ export class FileIconThemeLoader { result.hasFolderIcons = true; } - let rootFolder = associations.rootFolder || associations.folder; - let rootFolderExpanded = associations.rootFolderExpanded || associations.folderExpanded; + const rootFolder = associations.rootFolder || associations.folder; + const rootFolderExpanded = associations.rootFolderExpanded || associations.folderExpanded; if (rootFolder) { addSelector(`${qualifier} .rootfolder-icon::before`, rootFolder); @@ -289,37 +288,38 @@ export class FileIconThemeLoader { result.hasFileIcons = true; } - let folderNames = associations.folderNames; + const folderNames = associations.folderNames; if (folderNames) { - for (let folderName in folderNames) { + for (const folderName in folderNames) { addSelector(`${qualifier} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNames[folderName]); result.hasFolderIcons = true; } } - let folderNamesExpanded = associations.folderNamesExpanded; + const folderNamesExpanded = associations.folderNamesExpanded; if (folderNamesExpanded) { - for (let folderName in folderNamesExpanded) { + for (const folderName in folderNamesExpanded) { addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]); result.hasFolderIcons = true; } } - let languageIds = associations.languageIds; + const languageIds = associations.languageIds; if (languageIds) { if (!languageIds.jsonc && languageIds.json) { languageIds.jsonc = languageIds.json; } - for (let languageId in languageIds) { + for (const languageId in languageIds) { addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]); result.hasFileIcons = true; + hasSpecificFileIcons = true; coveredLanguages[languageId] = true; } } - let fileExtensions = associations.fileExtensions; + const fileExtensions = associations.fileExtensions; if (fileExtensions) { - for (let fileExtension in fileExtensions) { - let selectors: string[] = []; - let segments = fileExtension.toLowerCase().split('.'); + for (const fileExtension in fileExtensions) { + const selectors: string[] = []; + const segments = fileExtension.toLowerCase().split('.'); if (segments.length) { for (let i = 0; i < segments.length; i++) { selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); @@ -328,15 +328,16 @@ export class FileIconThemeLoader { } addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileExtensions[fileExtension]); result.hasFileIcons = true; + hasSpecificFileIcons = true; } } - let fileNames = associations.fileNames; + const fileNames = associations.fileNames; if (fileNames) { for (let fileName in fileNames) { - let selectors: string[] = []; + const selectors: string[] = []; fileName = fileName.toLowerCase(); selectors.push(`.${escapeCSS(fileName)}-name-file-icon`); - let segments = fileName.split('.'); + const segments = fileName.split('.'); if (segments.length) { for (let i = 1; i < segments.length; i++) { selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); @@ -345,6 +346,7 @@ export class FileIconThemeLoader { } addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[fileName]); result.hasFileIcons = true; + hasSpecificFileIcons = true; } } } @@ -357,61 +359,55 @@ export class FileIconThemeLoader { return result; } - let cssRules: string[] = []; + const showLanguageModeIcons = iconThemeDocument.showLanguageModeIcons === true || (hasSpecificFileIcons && iconThemeDocument.showLanguageModeIcons !== false); + + const cssRules: string[] = []; - let fonts = iconThemeDocument.fonts; + const fonts = iconThemeDocument.fonts; if (Array.isArray(fonts)) { fonts.forEach(font => { - let src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', '); + const src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', '); cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; font-display: block; }`); }); cssRules.push(`.show-file-icons .file-icon::before, .show-file-icons .folder-icon::before, .show-file-icons .rootfolder-icon::before { font-family: '${fonts[0].id}'; font-size: ${fonts[0].size || '150%'}; }`); } - for (let defId in selectorByDefinitionId) { - let selectors = selectorByDefinitionId[defId]; - let definition = iconThemeDocument.iconDefinitions[defId]; + for (const defId in selectorByDefinitionId) { + const selectors = selectorByDefinitionId[defId]; + const definition = iconThemeDocument.iconDefinitions[defId]; if (definition) { if (definition.iconPath) { cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: ${asCSSUrl(resolvePath(definition.iconPath))}; }`); - } - if (definition.fontCharacter || definition.fontColor) { - let body = ''; + } else if (definition.fontCharacter || definition.fontColor) { + const body = []; if (definition.fontColor) { - body += ` color: ${definition.fontColor};`; + body.push(`color: ${definition.fontColor};`); } if (definition.fontCharacter) { - body += ` content: '${definition.fontCharacter}';`; + body.push(`content: '${definition.fontCharacter}';`); } if (definition.fontSize) { - body += ` font-size: ${definition.fontSize};`; + body.push(`font-size: ${definition.fontSize};`); } if (definition.fontId) { - body += ` font-family: ${definition.fontId};`; + body.push(`font-family: ${definition.fontId};`); } - else if (Array.isArray(fonts)) { - body += ` font-family: ${fonts[0].id};`; + if (showLanguageModeIcons) { + body.push(`background-image: unset;`); // potentially set by the language default } - cssRules.push(`${selectors.join(', ')} { ${body} }`); + cssRules.push(`${selectors.join(', ')} { ${body.join(' ')} }`); } } } - if (iconThemeDocument.showLanguageModeIcons === true || (result.hasFileIcons && iconThemeDocument.showLanguageModeIcons !== false)) { - const iconRegistry = getIconRegistry(); + if (showLanguageModeIcons) { for (const languageId of this.modeService.getRegisteredLanguageIds()) { if (!coveredLanguages[languageId]) { - const iconName = this.modeService.getIcon(languageId); - if (iconName) { - const iconContribution = iconRegistry.getIcon(iconName.id); - if (iconContribution) { - const definition = this.themeService.getProductIconTheme().getIcon(iconContribution); - if (definition) { - const content = definition.fontCharacter; - const fontFamily = asCSSPropertyValue(definition.font?.id ?? 'codicon'); - cssRules.push(`.show-file-icons .${escapeCSS(languageId)}-lang-file-icon.file-icon::before { content: '${content}'; font-family: ${fontFamily}; font-size: 16px; background-image: none };`); - } - } + const icon = this.modeService.getIcon(languageId); + if (icon) { + const selector = `.show-file-icons .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`; + cssRules.push(`${selector} { content: ' '; background-image: ${asCSSUrl(icon.dark)}; }`); + cssRules.push(`.vs ${selector} { content: ' '; background-image: ${asCSSUrl(icon.light)}; }`); } } } diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index d5cf7085df331..9b560719561f6 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -127,7 +127,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.fileIconThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentFileIconTheme.bind(this)); this.fileIconThemeRegistry = new ThemeRegistry(fileIconThemesExtPoint, FileIconThemeData.fromExtensionTheme, true, FileIconThemeData.noIconTheme); - this.fileIconThemeLoader = new FileIconThemeLoader(extensionResourceLoaderService, modeService, this); + this.fileIconThemeLoader = new FileIconThemeLoader(extensionResourceLoaderService, modeService); this.onFileIconThemeChange = new Emitter(); this.currentFileIconTheme = FileIconThemeData.createUnloadedTheme(''); this.fileIconThemeSequencer = new Sequencer(); diff --git a/src/vscode-dts/vscode.proposed.languageIcon.d.ts b/src/vscode-dts/vscode.proposed.languageIcon.d.ts new file mode 100644 index 0000000000000..49d771d355235 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.languageIcon.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `icon` property of the language contribution point From f5d5c6863e72e4b61018eeeff9e525f625f42645 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 3 Jan 2022 17:42:44 +0100 Subject: [PATCH 0944/2210] debt - lift checksum service client to `workbench` --- .../services}/checksum/electron-sandbox/checksumService.ts | 0 src/vs/workbench/workbench.sandbox.main.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/vs/{platform => workbench/services}/checksum/electron-sandbox/checksumService.ts (100%) diff --git a/src/vs/platform/checksum/electron-sandbox/checksumService.ts b/src/vs/workbench/services/checksum/electron-sandbox/checksumService.ts similarity index 100% rename from src/vs/platform/checksum/electron-sandbox/checksumService.ts rename to src/vs/workbench/services/checksum/electron-sandbox/checksumService.ts diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index f3e2f530aaeca..03ff1f7147c52 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -71,10 +71,10 @@ import 'vs/workbench/services/timer/electron-sandbox/timerService'; import 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; import 'vs/workbench/services/integrity/electron-sandbox/integrityService'; import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService'; +import 'vs/workbench/services/checksum/electron-sandbox/checksumService'; import 'vs/platform/remote/electron-sandbox/sharedProcessTunnelService'; import 'vs/workbench/services/remote/electron-sandbox/tunnelService'; import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService'; -import 'vs/platform/checksum/electron-sandbox/checksumService'; import 'vs/platform/profiling/electron-sandbox/profilingService'; import 'vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService'; import 'vs/workbench/services/files/electron-sandbox/elevatedFileService'; From 4636c22e55a7d4ac49740257d72d2de060f5809a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Jan 2022 17:45:26 +0100 Subject: [PATCH 0945/2210] move things to electron-sandbox, https://github.com/microsoft/vscode/issues/111211 --- .../extensions.contribution.ts | 18 ------------------ .../extensionProfileService.ts | 2 +- .../extensions.contribution.ts | 10 ++++++++-- .../extensionsAutoProfiler.ts | 2 +- .../extensionHostProfiler.ts | 3 +-- src/vs/workbench/workbench.desktop.main.ts | 6 ------ 6 files changed, 11 insertions(+), 30 deletions(-) delete mode 100644 src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts rename src/vs/workbench/contrib/extensions/{electron-browser => electron-sandbox}/extensionProfileService.ts (99%) rename src/vs/workbench/contrib/extensions/{electron-browser => electron-sandbox}/extensionsAutoProfiler.ts (99%) rename src/vs/workbench/services/extensions/{electron-browser => electron-sandbox}/extensionHostProfiler.ts (96%) diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts deleted file mode 100644 index 555bb6af1163b..0000000000000 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Registry } from 'vs/platform/registry/common/platform'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor'; -import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-browser/extensionProfileService'; -import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; - -// Singletons -registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true); - -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts rename to src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts index 3cf8bb16787cf..c6efc4ff567ad 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts @@ -18,7 +18,7 @@ import { randomPort } from 'vs/base/common/ports'; import { IProductService } from 'vs/platform/product/common/productService'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; +import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService { diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts index 55cb5746c3c9c..19a6f4de8e869 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts @@ -12,7 +12,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { RuntimeExtensionsEditor, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED, SaveExtensionHostProfileAction } from 'vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor'; +import { RuntimeExtensionsEditor, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED, SaveExtensionHostProfileAction, IExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor'; import { DebugExtensionHostAction } from 'vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction'; import { IEditorSerializer, IEditorFactoryRegistry, ActiveEditorContext, EditorExtensions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; @@ -24,6 +24,12 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services import { ExtensionRecommendationNotificationServiceChannel } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; import { Codicon } from 'vs/base/common/codicons'; import { RemoteExtensionsInitializerContribution } from 'vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService'; +import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler'; + +// Singletons +registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true); // Running Extensions Editor Registry.as(EditorExtensions.EditorPane).registerEditorPane( @@ -61,8 +67,8 @@ class ExtensionsContributions implements IWorkbenchContribution { const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(RemoteExtensionsInitializerContribution, LifecyclePhase.Restored); - // Register Commands CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor) => { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts similarity index 99% rename from src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts rename to src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts index e7a5552d8e8b1..ec49247f46d54 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts @@ -19,7 +19,7 @@ import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/r import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { createSlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions'; -import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; +import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts similarity index 96% rename from src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts rename to src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts index 107d35a37cea9..a78d85ff21222 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { TernarySearchTree } from 'vs/base/common/map'; -import { realpathSync } from 'vs/base/node/extpath'; import { IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; @@ -39,7 +38,7 @@ export class ExtensionHostProfiler { let searchTree = TernarySearchTree.forUris(); for (let extension of extensions) { if (extension.extensionLocation.scheme === Schemas.file) { - searchTree.set(URI.file(realpathSync(extension.extensionLocation.fsPath)), extension); + searchTree.set(URI.file(extension.extensionLocation.fsPath), extension); } } diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index e8430fcb4ab49..ed5eada92aee5 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -94,12 +94,6 @@ import 'vs/workbench/services/extensions/electron-browser/extensionService'; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -// Extensions Management -import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; - - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: Please do NOT register services here. Use `registerSingleton()` From 43ed294c37dfd64523b1768afca74ae0f3be2271 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 3 Jan 2022 18:05:04 +0100 Subject: [PATCH 0946/2210] add showLanguageModeIcons to schema --- .../workbench/services/themes/common/fileIconThemeSchema.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts index 8654bf3955387..e274e77e541bb 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts @@ -223,6 +223,10 @@ const schema: IJSONSchema = { hidesExplorerArrows: { type: 'boolean', description: nls.localize('schema.hidesExplorerArrows', 'Configures whether the file explorer\'s arrows should be hidden when this theme is active.') + }, + showLanguageModeIcons: { + type: 'boolean', + description: nls.localize('schema.showLanguageModeIcons', 'Configures whether the default language icons should be used if the theme does not define an icon for a language.') } } }; From f88c6bd1dcd094d01f6dba6861cc197fdb2119c6 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 3 Jan 2022 18:05:43 +0100 Subject: [PATCH 0947/2210] add issue URL to proposed API d.ts --- src/vscode-dts/vscode.proposed.contribIcons.d.ts | 2 ++ src/vscode-dts/vscode.proposed.languageIcon.d.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/vscode-dts/vscode.proposed.contribIcons.d.ts b/src/vscode-dts/vscode.proposed.contribIcons.d.ts index 04f4fdc41e0fb..8d21c2a0712a1 100644 --- a/src/vscode-dts/vscode.proposed.contribIcons.d.ts +++ b/src/vscode-dts/vscode.proposed.contribIcons.d.ts @@ -4,3 +4,5 @@ *--------------------------------------------------------------------------------------------*/ // empty placeholder declaration for the `icons`-contribution point + +// https://github.com/microsoft/vscode/issues/119101 @aeschli diff --git a/src/vscode-dts/vscode.proposed.languageIcon.d.ts b/src/vscode-dts/vscode.proposed.languageIcon.d.ts index 49d771d355235..89b2358c028fc 100644 --- a/src/vscode-dts/vscode.proposed.languageIcon.d.ts +++ b/src/vscode-dts/vscode.proposed.languageIcon.d.ts @@ -4,3 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // empty placeholder declaration for the `icon` property of the language contribution point + +// https://github.com/microsoft/vscode/issues/14662 @aeschli + From 238c19cc7727511d495d67d2ce1e0ec0638de2ce Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 3 Jan 2022 11:12:14 -0800 Subject: [PATCH 0948/2210] Correct settings group aria label Fixes #140045 --- src/vs/workbench/contrib/preferences/browser/settingsTree.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 38fa06212ea24..08e2cb30c135f 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -2284,6 +2284,8 @@ class SettingsTreeAccessibilityProvider implements IListAccessibilityProvider Date: Mon, 3 Jan 2022 12:54:05 -0800 Subject: [PATCH 0949/2210] Only save active editor when it is untitled Fix #139880 --- src/vs/workbench/contrib/debug/common/debugUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index f9a59f1cba7cd..8fefc6931d27e 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -315,7 +315,7 @@ export async function saveAllBeforeDebugStart(configurationService: IConfigurati await editorService.saveAll(); if (saveBeforeStartConfig === 'allEditorsInActiveGroup') { const activeEditor = editorService.activeEditorPane; - if (activeEditor) { + if (activeEditor && activeEditor.input.resource?.scheme === Schemas.untitled) { // Make sure to save the active editor in case it is in untitled file it wont be saved as part of saveAll #111850 await editorService.save({ editor: activeEditor.input, groupId: activeEditor.group.id }); } From aa33d06f0ddf002f7843cf8b21ac1a46c678b17d Mon Sep 17 00:00:00 2001 From: Joel Jose Date: Tue, 4 Jan 2022 03:48:21 +0530 Subject: [PATCH 0950/2210] Fixes #140032 (#140046) Fixed typo in the dialog box shown when trying to close VS Code with multiple terminal instances open. --- src/vs/workbench/contrib/terminal/browser/terminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 7bf78b5edfa1b..02096f77cc690 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -858,7 +858,7 @@ export class TerminalService implements ITerminalService { if (this.instances.length === 1 || singleTerminal) { message = nls.localize('terminalService.terminalCloseConfirmationSingular', "Do you want to terminate the active terminal session?"); } else { - message = nls.localize('terminalService.terminalCloseConfirmationPlural', "Do you want to terminal the {0} active terminal sessions?", this.instances.length); + message = nls.localize('terminalService.terminalCloseConfirmationPlural', "Do you want to terminate the {0} active terminal sessions?", this.instances.length); } const res = await this._dialogService.confirm({ message, From 311a38c17c933e299dc7c4fd2fb1504b92f0d300 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 3 Jan 2022 14:46:27 -0800 Subject: [PATCH 0951/2210] Bump vscode-ripgrep in remote/ --- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/remote/package.json b/remote/package.json index d87bffd7e7756..09510660c0cc4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -21,7 +21,7 @@ "vscode-oniguruma": "1.6.1", "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", - "vscode-ripgrep": "^1.12.1", + "vscode-ripgrep": "^1.13.2", "vscode-textmate": "6.0.0", "xterm": "4.16.0", "xterm-addon-search": "0.9.0-beta.6", diff --git a/remote/yarn.lock b/remote/yarn.lock index 1313e80c3ff7b..a56700579e285 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -547,10 +547,10 @@ vscode-regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-ripgrep@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.12.1.tgz#4a319809d4010ea230659ce605fddacd1e36a589" - integrity sha512-4edKlcXNSKdC9mIQmQ9Wl25v0SF5DOK31JlvKHKHYV4co0V2MjI9pbDPdmogwbtiykz+kFV/cKnZH2TgssEasQ== +vscode-ripgrep@^1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.13.2.tgz#8ccebc33f14d54442c4b11962aead163c55b506e" + integrity sha512-RlK9U87EokgHfiOjDQ38ipQQX936gWOcWPQaJpYf+kAkz1PQ1pK2n7nhiscdOmLu6XGjTs7pWFJ/ckonpN7twQ== dependencies: https-proxy-agent "^4.0.0" proxy-from-env "^1.1.0" From e794a5444b93b08092167a206c69ad56f7a1d358 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 3 Jan 2022 14:16:37 -0800 Subject: [PATCH 0952/2210] Remove some "here" links to be more descriptive Ref #140059 --- build/monaco/README-npm.md | 3 +-- extensions/typescript-language-features/src/utils/tsconfig.ts | 4 ++-- src/vs/workbench/contrib/files/browser/files.contribution.ts | 4 ++-- .../workbench/contrib/search/browser/search.contribution.ts | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/build/monaco/README-npm.md b/build/monaco/README-npm.md index 737e06bbc5c4e..ca5592e0fe1f6 100644 --- a/build/monaco/README-npm.md +++ b/build/monaco/README-npm.md @@ -5,8 +5,7 @@ npm module and unless you are doing something special (e.g. authoring a monaco e and consumed independently), it is best to consume the [monaco-editor](https://www.npmjs.com/package/monaco-editor) module that contains this module and adds languages supports. -The Monaco Editor is the code editor that powers [VS Code](https://github.com/microsoft/vscode), -a good page describing the code editor's features is [here](https://code.visualstudio.com/docs/editor/editingevolved). +The Monaco Editor is the code editor that powers [VS Code](https://github.com/microsoft/vscode). Here is a good page describing some [editor features](https://code.visualstudio.com/docs/editor/editingevolved). This npm module contains the core editor functionality, as it comes from the [vscode repository](https://github.com/microsoft/vscode). diff --git a/extensions/typescript-language-features/src/utils/tsconfig.ts b/extensions/typescript-language-features/src/utils/tsconfig.ts index 0a9afbac6af79..d72d7489e3307 100644 --- a/extensions/typescript-language-features/src/utils/tsconfig.ts +++ b/extensions/typescript-language-features/src/utils/tsconfig.ts @@ -115,8 +115,8 @@ export async function openProjectConfigOrPromptToCreate( const selected = await vscode.window.showInformationMessage( (projectType === ProjectType.TypeScript - ? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project. Click [here]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=841896') - : localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project Click [here]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=759670') + ? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project. View the [tsconfig.json documentation]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=841896') + : localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project. View the [jsconfig.json documentation]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=759670') ), CreateConfigItem); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index da92ff67e0c69..4599241012b31 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -137,7 +137,7 @@ configurationRegistry.registerConfiguration({ 'properties': { [FILES_EXCLUDE_CONFIG]: { 'type': 'object', - 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the file Explorer decides which files and folders to show or hide based on this setting. Refer to the `#search.exclude#` setting to define search specific excludes. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + 'markdownDescription': nls.localize('exclude', "Configure [glob patterns](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options) for excluding files and folders. For example, the file explorer decides which files and folders to show or hide based on this setting. Refer to the `#search.exclude#` setting to define search-specific excludes."), 'default': { ...{ '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true, '**/Thumbs.db': true }, ...(isWeb ? { '**/*.crswap': true /* filter out swap files used for local file access */ } : undefined) @@ -234,7 +234,7 @@ configurationRegistry.registerConfiguration({ nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onWindowChange' }, "An editor with changes is automatically saved when the window loses focus.") ], 'default': isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF, - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of editors that have unsaved changes. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors that have unsaved changes.", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) }, 'files.autoSaveDelay': { 'type': 'number', diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 5390a2556d4cf..664fbe27c1337 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -824,7 +824,7 @@ configurationRegistry.registerConfiguration({ properties: { [SEARCH_EXCLUDE_CONFIG]: { type: 'object', - markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in fulltext searches and quick open. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + markdownDescription: nls.localize('exclude', "Configure [glob patterns](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options) for excluding files and folders in fulltext searches and quick open. Inherits all glob patterns from the `#files.exclude#` setting."), default: { '**/node_modules': true, '**/bower_components': true, '**/*.code-search': true }, additionalProperties: { anyOf: [ From 3fd7832ea53576123fbcbbfb3026af2900893a52 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 3 Jan 2022 15:02:44 -0800 Subject: [PATCH 0953/2210] Simplify unsupported natives module error message Ref #140059 --- .../services/extensions/node/extensionHostProcessSetup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 495fe601cc4f6..b27db09a4bb51 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -65,7 +65,7 @@ const args = minimist(process.argv.slice(2), { Module._load = function (request: string) { if (request === 'natives') { - throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more'); + throw new Error('Either the extension or an NPM dependency is using the [unsupported "natives" node module](https://go.microsoft.com/fwlink/?linkid=871887).'); } return originalLoad.apply(this, arguments); From bab81883a86a941f4d49abbee8b0eca3a572bf08 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 3 Jan 2022 16:31:59 -0800 Subject: [PATCH 0954/2210] update doc --- .../browser/docs/hybrid-find.drawio.svg | 327 ++++++++++++++++++ .../notebook/browser/docs/notebook.find.md | 24 ++ 2 files changed, 351 insertions(+) create mode 100644 src/vs/workbench/contrib/notebook/browser/docs/hybrid-find.drawio.svg create mode 100644 src/vs/workbench/contrib/notebook/browser/docs/notebook.find.md diff --git a/src/vs/workbench/contrib/notebook/browser/docs/hybrid-find.drawio.svg b/src/vs/workbench/contrib/notebook/browser/docs/hybrid-find.drawio.svg new file mode 100644 index 0000000000000..a2419b58fce4e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/docs/hybrid-find.drawio.svg @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + window.find +
    +
    +
    +
    + + window.find + +
    +
    + + + + + + +
    +
    +
    + exec("hiliteColor") +
    + findMatchColor +
    +
    +
    +
    + + exec("hiliteColor")... + +
    +
    + + + + +
    +
    +
    + Serialize +
    + document.getSelection() +
    +
    +
    +
    + + Serialize... + +
    +
    + + + + + + + + + + +
    +
    +
    + Find in Rendered Outputs (Search in DOM) +
    +
    +
    +
    + + Find in Rendered Out... + +
    +
    + + + + +
    +
    +
    + Find +
    +
    +
    +
    + + Find + +
    +
    + + + + +
    +
    +
    + Find in Text Model +
    +
    +
    +
    + + Find in Text Model + +
    +
    + + + + +
    +
    +
    + Mix matches +
    +
    +
    +
    + + Mix matches + +
    +
    + + + + +
    +
    +
    + End of Doc +
    +
    +
    +
    + + End of Doc + +
    +
    + + + + + + +
    +
    +
    + Webview +
    +
    +
    +
    + + Webview + +
    +
    + + + + + + +
    +
    +
    + Find Next +
    +
    +
    +
    + + Find Next + +
    +
    + + + + + + +
    +
    +
    + Is Model Match +
    +
    +
    +
    + + Is Model Match + +
    +
    + + + + +
    +
    +
    + Reveal Editor +
    +
    +
    +
    + + Reveal Editor + +
    +
    + + + + +
    +
    +
    + Y +
    +
    +
    +
    + + Y + +
    +
    + + + + + + +
    +
    +
    + document create range +
    +
    +
    +
    + + document create range + +
    +
    + + + + + + +
    +
    +
    + "hiliteColor" +
    + currentFindMatchColor +
    +
    +
    +
    + + "hiliteColor"... + +
    +
    + + + + + + +
    +
    +
    + Find cell/output container +
    +
    +
    +
    + + Find cell/output con... + +
    +
    +
    + + + + + Viewer does not support full SVG 1.1 + + + +
    diff --git a/src/vs/workbench/contrib/notebook/browser/docs/notebook.find.md b/src/vs/workbench/contrib/notebook/browser/docs/notebook.find.md new file mode 100644 index 0000000000000..e8c4bb9e5a71e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/docs/notebook.find.md @@ -0,0 +1,24 @@ + + +## Find in Outputs + +* Find is no longer **synchronous** + * Progress bar in Find Widget + * Require all outputs to be rendered first +* `window.find` + * Shadow DOM nodes are skipped in non-chromium browsers + * no builtin css rule for find match color +* `hiliteColor` + * Modifies DOM + * Shadow DOM not supported, we can potentially have our own impl +* Find match range serialization `document.getSelection` + * `document.getSelection().getRangeAt(0).cloneRange()` not immutable, changed when document active selection changes + * range will be invalid after `hiliteColor` executed + * require our own serialization/deserialization +* Performance + * `window.find` can be slow + * We currently travese the DOM tree to figure out which cell/output contain the find match belongs to, it's really costly. One idea is checking the absolute position of the find match and compare it with output container positions. + * Search only rendered outputs + * MutationObserver for output change + * Change active selection to the beginning of the new output and then request `window.find` + From 29360f3778960880c09ae64406064a8339d58ef5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 3 Jan 2022 16:59:46 -0800 Subject: [PATCH 0955/2210] Add progress bar for output search. --- src/vs/editor/contrib/find/findState.ts | 20 +++++++++++++++++-- .../browser/contrib/find/findController.ts | 11 +++++++++- .../browser/contrib/find/findModel.ts | 14 ++++++++----- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/find/findState.ts b/src/vs/editor/contrib/find/findState.ts index 0313dd8bc4630..32bf816362946 100644 --- a/src/vs/editor/contrib/find/findState.ts +++ b/src/vs/editor/contrib/find/findState.ts @@ -25,6 +25,7 @@ export interface FindReplaceStateChangedEvent { matchesCount: boolean; currentMatch: boolean; loop: boolean; + isSearching: boolean; } export const enum FindOptionOverride { @@ -48,6 +49,7 @@ export interface INewFindReplaceState { preserveCaseOverride?: FindOptionOverride; searchScope?: Range[] | null; loop?: boolean; + isSearching?: boolean; } function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean { @@ -78,6 +80,7 @@ export class FindReplaceState extends Disposable { private _matchesCount: number; private _currentMatch: Range | null; private _loop: boolean; + private _isSearching: boolean; private readonly _onFindReplaceStateChange = this._register(new Emitter()); public get searchString(): string { return this._searchString; } @@ -98,6 +101,7 @@ export class FindReplaceState extends Disposable { public get matchesPosition(): number { return this._matchesPosition; } public get matchesCount(): number { return this._matchesCount; } public get currentMatch(): Range | null { return this._currentMatch; } + public get isSearching(): boolean { return this._isSearching; } public readonly onFindReplaceStateChange: Event = this._onFindReplaceStateChange.event; constructor() { @@ -119,6 +123,7 @@ export class FindReplaceState extends Disposable { this._matchesCount = 0; this._currentMatch = null; this._loop = true; + this._isSearching = false; } public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void { @@ -137,7 +142,8 @@ export class FindReplaceState extends Disposable { matchesPosition: false, matchesCount: false, currentMatch: false, - loop: false + loop: false, + isSearching: false }; let somethingChanged = false; @@ -188,7 +194,8 @@ export class FindReplaceState extends Disposable { matchesPosition: false, matchesCount: false, currentMatch: false, - loop: false + loop: false, + isSearching: false }; let somethingChanged = false; @@ -255,6 +262,15 @@ export class FindReplaceState extends Disposable { somethingChanged = true; } } + + if (typeof newState.isSearching !== 'undefined') { + if (this._isSearching !== newState.isSearching) { + this._isSearching = newState.isSearching; + changeEvent.isSearching = true; + somethingChanged = true; + } + } + // Overrides get set when they explicitly come in and get reset anytime something else changes this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet); this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index c6b81253e230b..e6f51385d9561 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -63,8 +63,16 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this.updateTheme(themeService.getColorTheme()); })); - this._register(this._state.onFindReplaceStateChange(() => { + this._register(this._state.onFindReplaceStateChange((e) => { this.onInputChanged(); + + if (e.isSearching) { + if (this._state.isSearching) { + this._progressBar.infinite().show(); + } else { + this._progressBar.stop(); + } + } })); this._register(DOM.addDisposableListener(this.getDomNode(), DOM.EventType.FOCUS, e => { @@ -221,6 +229,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this._state.change({ isRevealed: false }, false); this._findModel.clear(); this._notebookEditor.findStop(); + this._progressBar.stop(); if (this._hideTimeout === null) { if (this._showTimeout !== null) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index 783a01efa7cb7..c90022a119798 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -289,19 +289,23 @@ export class FindModel extends Disposable { } private async _compute(token: CancellationToken): Promise { + this._state.change({ isSearching: true }, false); + let ret: CellFindMatchWithIndex[] | null = null; const val = this._state.searchString; const wordSeparators = this._configurationService.inspect('editor.wordSeparators').value; const options: INotebookSearchOptions = { regex: this._state.isRegex, wholeWord: this._state.wholeWord, caseSensitive: this._state.matchCase, wordSeparators: wordSeparators }; if (!val) { - return null; + ret = null; + } else if (!this._notebookEditor.hasModel()) { + ret = null; + } else { + ret = await this._notebookEditor.find(val, options, token); } - if (!this._notebookEditor.hasModel()) { - return null; - } + this._state.change({ isSearching: false }, false); + return ret; - return this._notebookEditor.find(val, options, token); } private _updateCurrentMatch(findMatches: CellFindMatchWithIndex[], currentMatchesPosition: number) { From 7f906a2efef358014da9e80672bb44bf7c07abfc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 3 Jan 2022 17:46:01 -0800 Subject: [PATCH 0956/2210] Fix stuck progress bar on notebook editor --- .../notebook/browser/contrib/execute/executionEditorProgress.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress.ts b/src/vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress.ts index 1560fba1f3f5d..fc628ff29b4b8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress.ts @@ -22,7 +22,7 @@ export class ExecutionEditorProgressController extends Disposable implements INo this._register(_notebookEditor.onDidChangeVisibleRanges(() => this._update())); this._register(_notebookExecutionStateService.onDidChangeCellExecution(e => { - if (e.notebook !== this._notebookEditor.textModel?.uri) { + if (e.notebook.toString() !== this._notebookEditor.textModel?.uri.toString()) { return; } From 1e5cffe81b696ea25420b1ce7ad15d577986cebc Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 3 Jan 2022 17:52:39 -0800 Subject: [PATCH 0957/2210] Remove click here links for bot messages (#140066) Fixes #140059 --- .github/commands.json | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/commands.json b/.github/commands.json index 21e452e25f00f..68a50baae9577 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -15,7 +15,7 @@ "type": "label", "name": "*question", "action": "close", - "comment": "We closed this issue because it is a question about using VS Code rather than an issue or feature request. Please search for help on [StackOverflow](https://aka.ms/vscodestackoverflow), where the community has already answered thousands of similar questions. You may find their [guide on asking a new question](https://aka.ms/vscodestackoverflowquestion) helpful if your question has not already been asked. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + "comment": "We closed this issue because it is a question about using VS Code rather than an issue or feature request. Please search for help on [StackOverflow](https://aka.ms/vscodestackoverflow), where the community has already answered thousands of similar questions. You may find their [guide on asking a new question](https://aka.ms/vscodestackoverflowquestion) helpful if your question has not already been asked. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "label", @@ -27,19 +27,19 @@ "type": "label", "name": "*extension-candidate", "action": "close", - "comment": "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + "comment": "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "label", "name": "*not-reproducible", "action": "close", - "comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines might help you with that.\n\nHappy Coding!" + "comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) might help you with that.\n\nHappy Coding!" }, { "type": "label", "name": "*out-of-scope", "action": "close", - "comment": "We closed this issue because we don't plan to address it in the foreseeable future. You can find more detailed information about our decision-making process [here](https://aka.ms/vscode-out-of-scope). If you disagree and feel that this issue is crucial: We are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nThanks for your understanding and happy coding!" + "comment": "We closed this issue because we [don't plan to address it](https://aka.ms/vscode-out-of-scope) in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nThanks for your understanding, and happy coding!" }, { "type": "comment", @@ -57,13 +57,13 @@ "type": "label", "name": "*caused-by-extension", "action": "close", - "comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + "comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "label", "name": "*as-designed", "action": "close", - "comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + "comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "label", @@ -104,7 +104,7 @@ "type": "label", "name": "*duplicate", "action": "close", - "comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](${duplicateQuery}). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + "comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for [similar existing issues](${duplicateQuery}). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "comment", @@ -193,7 +193,7 @@ "action": "updateLabels", "addLabel": "needs more info", "removeLabel": "~needs more info", - "comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" + "comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!" }, { "type": "label", @@ -201,7 +201,7 @@ "action": "updateLabels", "addLabel": "needs more info", "removeLabel": "~needs version info", - "comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" + "comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!" }, { "type": "comment", @@ -222,7 +222,7 @@ "type": "label", "name": "*off-topic", "action": "close", - "comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + "comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "comment", @@ -235,7 +235,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Python extension. Please file it with the repository [here](https://github.com/microsoft/vscode-python). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the Python extension. Please file the issue to the [Python extension repository](https://github.com/microsoft/vscode-python). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -248,7 +248,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Jupyter extension. Please file it with the repository [here](https://github.com/microsoft/vscode-jupyter). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the Jupyter extension. Please file the issue to the [Jupyter extension repository](https://github.com/microsoft/vscode-jupyter). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -261,7 +261,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the C extension. Please file it with the repository [here](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the C extension. Please file the issue to the [C extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -274,7 +274,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the C++ extension. Please file it with the repository [here](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -287,7 +287,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the C++ extension. Please file it with the repository [here](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -300,7 +300,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the TypeScript language service. Please file it with the repository [here](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the TypeScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -313,7 +313,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the TypeScript/JavaScript language service. Please file it with the repository [here](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the TypeScript/JavaScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -326,7 +326,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the C# extension. Please file it with the repository [here](https://github.com/OmniSharp/omnisharp-vscode.git). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the C# extension. Please file the issue to the [C# extension repository](https://github.com/OmniSharp/omnisharp-vscode.git). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -339,7 +339,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Go extension. Please file it with the repository [here](https://github.com/golang/vscode-go). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the Go extension. Please file the issue to the [Go extension repository](https://github.com/golang/vscode-go). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -352,7 +352,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the PowerShell extension. Please file it with the repository [here](https://github.com/PowerShell/vscode-powershell). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the PowerShell extension. Please file the issue to the [PowerShell extension repository](https://github.com/PowerShell/vscode-powershell). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -365,7 +365,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the LiveShare extension. Please file it with the repository [here](https://github.com/MicrosoftDocs/live-share). Make sure to check their [contributing guidelines](https://github.com/MicrosoftDocs/live-share/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the LiveShare extension. Please file the issue to the [LiveShare repository](https://github.com/MicrosoftDocs/live-share). Make sure to check their [contributing guidelines](https://github.com/MicrosoftDocs/live-share/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -378,7 +378,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Docker extension. Please file it with the repository [here](https://github.com/microsoft/vscode-docker). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the Docker extension. Please file the issue to the [Docker extension repository](https://github.com/microsoft/vscode-docker). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -391,7 +391,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Java extension. Please file it with the repository [here](https://github.com/redhat-developer/vscode-java). Make sure to check their [troubleshooting instructions](https://github.com/redhat-developer/vscode-java/wiki/Troubleshooting) and provide relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the Java extension. Please file the issue to the [Java extension repository](https://github.com/redhat-developer/vscode-java). Make sure to check their [troubleshooting instructions](https://github.com/redhat-developer/vscode-java/wiki/Troubleshooting) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", @@ -404,7 +404,7 @@ ], "action": "close", "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Java Debugger extension. Please file it with the repository [here](https://github.com/microsoft/vscode-java-debug). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + "comment": "It looks like this is caused by the Java Debugger extension. Please file the issue to the [Java Debugger repository](https://github.com/microsoft/vscode-java-debug). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, { "type": "comment", From 990ad4044032b05c0ebf0f67f800c5845baad4c7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 3 Jan 2022 18:19:47 -0800 Subject: [PATCH 0958/2210] Don't overwrite iframes' pointer-events style property Fix #137549 --- src/vs/base/browser/ui/sash/sash.css | 4 ++++ src/vs/base/browser/ui/sash/sash.ts | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index eb0aa0d6b689c..687172eed5bb4 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -121,6 +121,10 @@ top: calc(50% - (var(--sash-hover-size) / 2)); } +.pointerEventsDisabled { + pointer-events: none !important; +} + /** Debug **/ .monaco-sash.debug { diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index d0e68ce6bd9d0..94380394d5bb1 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -222,6 +222,8 @@ class OrthogonalPointerEventFactory implements IPointerEventFactory { } } +const PointerEventsDisabledCssClass = 'pointerEventsDisabled'; + /** * The {@link Sash} is the UI component which allows the user to resize other * components. It's usually an invisible horizontal or vertical line which, when @@ -493,7 +495,7 @@ export class Sash extends Disposable { const iframes = getElementsByTagName('iframe'); for (const iframe of iframes) { - iframe.style.pointerEvents = 'none'; // disable mouse events on iframes as long as we drag the sash + iframe.classList.add(PointerEventsDisabledCssClass); // disable mouse events on iframes as long as we drag the sash } const startX = event.pageX; @@ -558,7 +560,7 @@ export class Sash extends Disposable { disposables.dispose(); for (const iframe of iframes) { - iframe.style.pointerEvents = 'auto'; + iframe.classList.remove(PointerEventsDisabledCssClass); } }; From 5524c0d1c0434e8c263f490d17e2f0a7bc954145 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Jan 2022 06:31:56 +0100 Subject: [PATCH 0959/2210] Explorer file name validation hardcodes OS (fix #139800) (#139962) --- .../contrib/files/browser/fileActions.ts | 24 ++++--- .../files/test/browser/explorerModel.test.ts | 71 ++++++++++--------- .../configurationResolverService.test.ts | 4 +- .../services/path/common/pathService.ts | 22 ++++-- .../test/browser/workbenchTestServices.ts | 4 +- 5 files changed, 76 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index e88182a2dac80..b3e8ad245f2ce 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { isWindows } from 'vs/base/common/platform'; -import * as extpath from 'vs/base/common/extpath'; +import { isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; import { extname, basename } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -50,6 +49,8 @@ import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { BrowserFileUpload, FileDownload } from 'vs/workbench/contrib/files/browser/fileImportExport'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -621,7 +622,7 @@ export class ShowOpenedFileInNewWindow extends Action { } } -export function validateFileName(item: ExplorerItem, name: string): { content: string, severity: Severity } | null { +export function validateFileName(pathService: IPathService, item: ExplorerItem, name: string, os: OperatingSystem): { content: string, severity: Severity } | null { // Produce a well formed file name name = getWellFormedFileName(name); @@ -655,9 +656,8 @@ export function validateFileName(item: ExplorerItem, name: string): { content: s } } - // Invalid File name - const windowsBasenameValidity = item.resource.scheme === Schemas.file && isWindows; - if (names.some((folderName) => !extpath.isValidBasename(folderName, windowsBasenameValidity))) { + // Check for invalid file name. + if (names.some(folderName => !pathService.hasValidBasename(item.resource, os, folderName))) { return { content: nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)), severity: Severity.Error @@ -780,7 +780,9 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const editorService = accessor.get(IEditorService); const viewsService = accessor.get(IViewsService); const notificationService = accessor.get(INotificationService); + const remoteAgentService = accessor.get(IRemoteAgentService); const commandService = accessor.get(ICommandService); + const pathService = accessor.get(IPathService); const wasHidden = !viewsService.isViewVisible(VIEW_ID); const view = await viewsService.openView(VIEW_ID, true); @@ -834,8 +836,10 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole } }; + const os = (await remoteAgentService.getEnvironment())?.os ?? OS; + await explorerService.setEditable(newStat, { - validationMessage: value => validateFileName(newStat, value), + validationMessage: value => validateFileName(pathService, newStat, value, os), onFinish: async (value, success) => { folder.removeChild(newStat); await explorerService.setEditable(newStat, null); @@ -863,6 +867,8 @@ CommandsRegistry.registerCommand({ export const renameHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const notificationService = accessor.get(INotificationService); + const remoteAgentService = accessor.get(IRemoteAgentService); + const pathService = accessor.get(IPathService); const stats = explorerService.getContext(false); const stat = stats.length > 0 ? stats[0] : undefined; @@ -870,8 +876,10 @@ export const renameHandler = async (accessor: ServicesAccessor) => { return; } + const os = (await remoteAgentService.getEnvironment())?.os ?? OS; + await explorerService.setEditable(stat, { - validationMessage: value => validateFileName(stat, value), + validationMessage: value => validateFileName(pathService, stat, value, os), onFinish: async (value, success) => { if (success) { const parentResource = stat.parent!.resource; diff --git a/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts index 877158e416fb9..622264730e32a 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts @@ -4,21 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { isLinux, isWindows } from 'vs/base/common/platform'; +import { isLinux, isWindows, OS } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { join } from 'vs/base/common/path'; import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { toResource } from 'vs/base/test/common/utils'; -import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestFileService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices'; -const fileService = new TestFileService(); -function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, false, false, name, mtime); -} suite('Files - View Model', function () { + const fileService = new TestFileService(); + function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { + return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, false, false, name, mtime); + } + + const pathService = new TestPathService(); + test('Properties', function () { const d = new Date().getTime(); let s = createStat.call(this, '/path/to/stat', 'sName', true, true, 8096, d); @@ -183,23 +186,23 @@ suite('Files - View Model', function () { const sChild = createStat.call(this, '/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); s.addChild(sChild); - assert(validateFileName(s, null!) !== null); - assert(validateFileName(s, '') !== null); - assert(validateFileName(s, ' ') !== null); - assert(validateFileName(s, 'Read Me') === null, 'name containing space'); + assert(validateFileName(pathService, s, null!, OS) !== null); + assert(validateFileName(pathService, s, '', OS) !== null); + assert(validateFileName(pathService, s, ' ', OS) !== null); + assert(validateFileName(pathService, s, 'Read Me', OS) === null, 'name containing space'); if (isWindows) { - assert(validateFileName(s, 'foo:bar') !== null); - assert(validateFileName(s, 'foo*bar') !== null); - assert(validateFileName(s, 'foo?bar') !== null); - assert(validateFileName(s, 'foobar') !== null); - assert(validateFileName(s, 'foo|bar') !== null); + assert(validateFileName(pathService, s, 'foo:bar', OS) !== null); + assert(validateFileName(pathService, s, 'foo*bar', OS) !== null); + assert(validateFileName(pathService, s, 'foo?bar', OS) !== null); + assert(validateFileName(pathService, s, 'foobar', OS) !== null); + assert(validateFileName(pathService, s, 'foo|bar', OS) !== null); } - assert(validateFileName(s, 'alles.klar') === null); - assert(validateFileName(s, '.foo') === null); - assert(validateFileName(s, 'foo.bar') === null); - assert(validateFileName(s, 'foo') === null); + assert(validateFileName(pathService, s, 'alles.klar', OS) === null); + assert(validateFileName(pathService, s, '.foo', OS) === null); + assert(validateFileName(pathService, s, 'foo.bar', OS) === null); + assert(validateFileName(pathService, s, 'foo', OS) === null); }); test('Validate File Name (For Rename)', function () { @@ -208,25 +211,25 @@ suite('Files - View Model', function () { const sChild = createStat.call(this, '/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); s.addChild(sChild); - assert(validateFileName(s, 'alles.klar') === null); + assert(validateFileName(pathService, s, 'alles.klar', OS) === null); - assert(validateFileName(s, 'Alles.klar') === null); - assert(validateFileName(s, 'Alles.Klar') === null); + assert(validateFileName(pathService, s, 'Alles.klar', OS) === null); + assert(validateFileName(pathService, s, 'Alles.Klar', OS) === null); - assert(validateFileName(s, '.foo') === null); - assert(validateFileName(s, 'foo.bar') === null); - assert(validateFileName(s, 'foo') === null); + assert(validateFileName(pathService, s, '.foo', OS) === null); + assert(validateFileName(pathService, s, 'foo.bar', OS) === null); + assert(validateFileName(pathService, s, 'foo', OS) === null); }); test('Validate Multi-Path File Names', function () { const d = new Date().getTime(); const wsFolder = createStat.call(this, '/', 'workspaceFolder', true, false, 8096, d); - assert(validateFileName(wsFolder, 'foo/bar') === null); - assert(validateFileName(wsFolder, 'foo\\bar') === null); - assert(validateFileName(wsFolder, 'all/slashes/are/same') === null); - assert(validateFileName(wsFolder, 'theres/one/different\\slash') === null); - assert(validateFileName(wsFolder, '/slashAtBeginning') !== null); + assert(validateFileName(pathService, wsFolder, 'foo/bar', OS) === null); + assert(validateFileName(pathService, wsFolder, 'foo\\bar', OS) === null); + assert(validateFileName(pathService, wsFolder, 'all/slashes/are/same', OS) === null); + assert(validateFileName(pathService, wsFolder, 'theres/one/different\\slash', OS) === null); + assert(validateFileName(pathService, wsFolder, '/slashAtBeginning', OS) !== null); // attempting to add a child to a deeply nested file const s1 = createStat.call(this, '/path', 'path', true, false, 8096, d); @@ -237,11 +240,11 @@ suite('Files - View Model', function () { s2.addChild(s3); const fileDeeplyNested = createStat.call(this, '/path/to/stat/fileNested', 'fileNested', false, false, 8096, d); s3.addChild(fileDeeplyNested); - assert(validateFileName(wsFolder, '/path/to/stat/fileNested/aChild') !== null); + assert(validateFileName(pathService, wsFolder, '/path/to/stat/fileNested/aChild', OS) !== null); // detect if path already exists - assert(validateFileName(wsFolder, '/path/to/stat/fileNested') !== null); - assert(validateFileName(wsFolder, '/path/to/stat/') !== null); + assert(validateFileName(pathService, wsFolder, '/path/to/stat/fileNested', OS) !== null); + assert(validateFileName(pathService, wsFolder, '/path/to/stat/', OS) !== null); }); test('Merge Local with Disk', function () { diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index f33be029fc706..cd7dd738abb75 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -694,7 +694,9 @@ class MockPathService implements IPathService { userHome(options?: { preferLocal: boolean; }): Promise { throw new Error('Method not implemented.'); } - hasValidBasename(resource: uri): Promise { + hasValidBasename(resource: uri, basename?: string): Promise; + hasValidBasename(resource: uri, os: platform.OperatingSystem, basename?: string): boolean; + hasValidBasename(resource: uri, arg2?: string | platform.OperatingSystem, name?: string): boolean | Promise { throw new Error('Method not implemented.'); } resolvedUserHome: uri | undefined; diff --git a/src/vs/workbench/services/path/common/pathService.ts b/src/vs/workbench/services/path/common/pathService.ts index cf661593d07fa..e3003230e65a4 100644 --- a/src/vs/workbench/services/path/common/pathService.ts +++ b/src/vs/workbench/services/path/common/pathService.ts @@ -68,7 +68,8 @@ export interface IPathService { * these OS. Other remotes are not supported and this method * will always return `true` for them. */ - hasValidBasename(resource: URI): Promise; + hasValidBasename(resource: URI, basename?: string): Promise; + hasValidBasename(resource: URI, os: OperatingSystem, basename?: string): boolean; /** * @deprecated use `userHome` instead. @@ -109,15 +110,26 @@ export abstract class AbstractPathService implements IPathService { })(); } - async hasValidBasename(resource: URI): Promise { + hasValidBasename(resource: URI, basename?: string): Promise; + hasValidBasename(resource: URI, os: OperatingSystem, basename?: string): boolean; + hasValidBasename(resource: URI, arg2?: string | OperatingSystem, basename?: string): boolean | Promise { + + // async version + if (typeof arg2 === 'string' || typeof arg2 === 'undefined') { + return this.resolveOS.then(os => this.doHasValidBasename(resource, os, basename)); + } + + // sync version + return this.doHasValidBasename(resource, arg2, basename); + } + + private doHasValidBasename(resource: URI, os: OperatingSystem, name?: string): boolean { // Our `isValidBasename` method only works with our // standard schemes for files on disk, either locally // or remote. if (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote) { - const os = await this.resolveOS; - - return isValidBasename(basename(resource), os === OperatingSystem.Windows); + return isValidBasename(name ?? basename(resource), os === OperatingSystem.Windows); } return true; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 7b5da5617326b..649959a39786d 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1680,7 +1680,9 @@ export class TestPathService implements IPathService { constructor(private readonly fallbackUserHome: URI = URI.from({ scheme: Schemas.vscodeRemote, path: '/' })) { } - async hasValidBasename(resource: URI): Promise { + hasValidBasename(resource: URI, basename?: string): Promise; + hasValidBasename(resource: URI, os: OperatingSystem, basename?: string): boolean; + hasValidBasename(resource: URI, arg2?: string | OperatingSystem, name?: string): boolean | Promise { return isValidBasename(basename(resource)); } From 2ddc3c581cbb5b94d0abd14a3746122e73d88cea Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Jan 2022 06:36:52 +0100 Subject: [PATCH 0960/2210] files - use `fs.promises.rename` for `rimraf` to skip `graceful-fs` We do not really want the move operation to hang for a long time when the folder is locked on Windows. --- src/vs/base/node/pfs.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 3804f5d227129..7ea4c75c94b1c 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -56,7 +56,15 @@ async function rimrafMove(path: string): Promise { try { const pathInTemp = randomPath(tmpdir()); try { - await Promises.rename(path, pathInTemp); + // Intentionally using `fs.promises` here to skip + // the patched graceful-fs method that can result + // in very long running `rename` calls when the + // folder is locked by a file watcher. We do not + // really want to slow down this operation more + // than necessary and we have a fallback to delete + // via unlink. + // https://github.com/microsoft/vscode/issues/139908 + await fs.promises.rename(path, pathInTemp); } catch (error) { return rimrafUnlink(path); // if rename fails, delete without tmp dir } From 5091aa14eeecdc67089ac6ccc2c027e620d66148 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Jan 2022 07:44:11 +0100 Subject: [PATCH 0961/2210] fix tests --- src/vs/workbench/services/path/common/pathService.ts | 2 +- src/vs/workbench/test/browser/workbenchTestServices.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/path/common/pathService.ts b/src/vs/workbench/services/path/common/pathService.ts index e3003230e65a4..8261fca792574 100644 --- a/src/vs/workbench/services/path/common/pathService.ts +++ b/src/vs/workbench/services/path/common/pathService.ts @@ -116,7 +116,7 @@ export abstract class AbstractPathService implements IPathService { // async version if (typeof arg2 === 'string' || typeof arg2 === 'undefined') { - return this.resolveOS.then(os => this.doHasValidBasename(resource, os, basename)); + return this.resolveOS.then(os => this.doHasValidBasename(resource, os, arg2)); } // sync version diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 649959a39786d..5e9c9e87bee44 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1683,7 +1683,11 @@ export class TestPathService implements IPathService { hasValidBasename(resource: URI, basename?: string): Promise; hasValidBasename(resource: URI, os: OperatingSystem, basename?: string): boolean; hasValidBasename(resource: URI, arg2?: string | OperatingSystem, name?: string): boolean | Promise { - return isValidBasename(basename(resource)); + if (typeof arg2 === 'string' || typeof arg2 === 'undefined') { + return isValidBasename(arg2 ?? basename(resource)); + } + + return isValidBasename(name ?? basename(resource)); } get path() { return Promise.resolve(isWindows ? win32 : posix); } From e5d7e8f1e61fcbff3f4674d959d1436596aa51af Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 3 Jan 2022 23:08:59 -0800 Subject: [PATCH 0962/2210] Adopt EditorInputCapabilities.Singleton Ref #139837 --- .../contrib/welcome/walkThrough/browser/walkThroughInput.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts index 033578ecaf765..6258caecf57f7 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts @@ -13,7 +13,7 @@ import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { requireToContent } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider'; import { Dimension } from 'vs/base/browser/dom'; -import { IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { EditorInputCapabilities, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class WalkThroughModel extends EditorModel { @@ -51,6 +51,10 @@ export interface WalkThroughInputOptions { export class WalkThroughInput extends EditorInput { + override get capabilities(): EditorInputCapabilities { + return EditorInputCapabilities.Singleton | super.capabilities; + } + private promise: Promise | null = null; private maxTopScroll = 0; From b8d6d8667a0a45a4322d051ecdc51d757bdd8074 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Jan 2022 09:11:44 +0100 Subject: [PATCH 0963/2210] Reimplement node.js watcher and add tests (#140073) * files - use `fs.promises.rename` for `rimraf` to skip `graceful-fs` We do not really want the move operation to hang for a long time when the folder is locked on Windows. * watcher - rewrite non-recursive watcher and add tests * fixes * fix tests --- src/vs/base/node/extpath.ts | 41 ++ src/vs/base/node/watcher.ts | 264 ----------- src/vs/base/test/node/extpath.test.ts | 28 +- src/vs/code/node/cli.ts | 2 +- .../files/common/diskFileSystemProvider.ts | 37 +- src/vs/platform/files/common/watcher.ts | 27 +- .../diskFileSystemProviderServer.ts | 12 +- .../files/node/diskFileSystemProvider.ts | 10 +- .../node/diskFileSystemProviderServer.ts | 8 +- .../node/watcher/nodejs/nodejsWatcher.ts | 427 ++++++++++++++--- .../node/watcher/parcel/parcelWatcher.ts | 36 +- .../node/nodejsWatcher.integrationTest.ts | 435 ++++++++++++++++++ .../node/parcelWatcher.integrationTest.ts | 3 +- .../server/remoteFileSystemProviderServer.ts | 4 +- .../contrib/files/browser/workspaceWatcher.ts | 4 +- 15 files changed, 949 insertions(+), 389 deletions(-) delete mode 100644 src/vs/base/node/watcher.ts create mode 100644 src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts diff --git a/src/vs/base/node/extpath.ts b/src/vs/base/node/extpath.ts index 5e225fd981994..ee8f3f4eb31db 100644 --- a/src/vs/base/node/extpath.ts +++ b/src/vs/base/node/extpath.ts @@ -58,6 +58,46 @@ export function realcaseSync(path: string): string | null { return null; } +export async function realcase(path: string): Promise { + if (isLinux) { + // This method is unsupported on OS that have case sensitive + // file system where the same path can exist in different forms + // (see also https://github.com/microsoft/vscode/issues/139709) + return path; + } + + const dir = dirname(path); + if (path === dir) { // end recursion + return path; + } + + const name = (basename(path) /* can be '' for windows drive letters */ || path).toLowerCase(); + try { + const entries = await Promises.readdir(dir); + const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search + if (found.length === 1) { + // on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition + const prefix = await realcase(dir); // recurse + if (prefix) { + return join(prefix, found[0]); + } + } else if (found.length > 1) { + // must be a case sensitive $filesystem + const ix = found.indexOf(name); + if (ix >= 0) { // case sensitive + const prefix = await realcase(dir); // recurse + if (prefix) { + return join(prefix, found[ix]); + } + } + } + } catch (error) { + // silently ignore error + } + + return null; +} + export async function realpath(path: string): Promise { try { // DO NOT USE `fs.promises.realpath` here as it internally @@ -91,6 +131,7 @@ export function realpathSync(path: string): string { // fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is // to not resolve links but to simply see if the path is read accessible or not. const normalizedPath = normalizePath(path); + fs.accessSync(normalizedPath, fs.constants.R_OK); // throws in case of an error return normalizedPath; diff --git a/src/vs/base/node/watcher.ts b/src/vs/base/node/watcher.ts deleted file mode 100644 index 58adf3749438d..0000000000000 --- a/src/vs/base/node/watcher.ts +++ /dev/null @@ -1,264 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { watch } from 'fs'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { isEqualOrParent } from 'vs/base/common/extpath'; -import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { normalizeNFC } from 'vs/base/common/normalization'; -import { basename, join } from 'vs/base/common/path'; -import { isMacintosh } from 'vs/base/common/platform'; -import { Promises } from 'vs/base/node/pfs'; - -export function watchFile(path: string, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable { - return doWatchNonRecursive({ path, isDirectory: false }, onChange, onError); -} - -export function watchFolder(path: string, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable { - return doWatchNonRecursive({ path, isDirectory: true }, onChange, onError); -} - -export const CHANGE_BUFFER_DELAY = 100; - -function doWatchNonRecursive(file: { path: string, isDirectory: boolean }, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable { - - // macOS: watching samba shares can crash VSCode so we do - // a simple check for the file path pointing to /Volumes - // (https://github.com/microsoft/vscode/issues/106879) - // TODO@electron this needs a revisit when the crash is - // fixed or mitigated upstream. - if (isMacintosh && isEqualOrParent(file.path, '/Volumes/')) { - onError(`Refusing to watch ${file.path} for changes using fs.watch() for possibly being a network share where watching is unreliable and unstable.`); - return Disposable.None; - } - - const originalFileName = basename(file.path); - const mapPathToStatDisposable = new Map(); - - let disposed = false; - let watcherDisposables: IDisposable[] = [toDisposable(() => { - mapPathToStatDisposable.forEach(disposable => dispose(disposable)); - mapPathToStatDisposable.clear(); - })]; - - try { - - // Creating watcher can fail with an exception - const watcher = watch(file.path); - watcherDisposables.push(toDisposable(() => { - watcher.removeAllListeners(); - watcher.close(); - })); - - // Folder: resolve children to emit proper events - const folderChildren: Set = new Set(); - if (file.isDirectory) { - Promises.readdir(file.path).then(children => children.forEach(child => folderChildren.add(child))); - } - - watcher.on('error', (code: number, signal: string) => { - if (!disposed) { - onError(`Failed to watch ${file.path} for changes using fs.watch() (${code}, ${signal})`); - } - }); - - watcher.on('change', (type, raw) => { - if (disposed) { - return; // ignore if already disposed - } - - // Normalize file name - let changedFileName: string = ''; - if (raw) { // https://github.com/microsoft/vscode/issues/38191 - changedFileName = raw.toString(); - if (isMacintosh) { - // Mac: uses NFD unicode form on disk, but we want NFC - // See also https://github.com/nodejs/node/issues/2165 - changedFileName = normalizeNFC(changedFileName); - } - } - - if (!changedFileName || (type !== 'change' && type !== 'rename')) { - return; // ignore unexpected events - } - - // File path: use path directly for files and join with changed file name otherwise - const changedFilePath = file.isDirectory ? join(file.path, changedFileName) : file.path; - - // File - if (!file.isDirectory) { - if (type === 'rename' || changedFileName !== originalFileName) { - // The file was either deleted or renamed. Many tools apply changes to files in an - // atomic way ("Atomic Save") by first renaming the file to a temporary name and then - // renaming it back to the original name. Our watcher will detect this as a rename - // and then stops to work on Mac and Linux because the watcher is applied to the - // inode and not the name. The fix is to detect this case and trying to watch the file - // again after a certain delay. - // In addition, we send out a delete event if after a timeout we detect that the file - // does indeed not exist anymore. - - const timeoutHandle = setTimeout(async () => { - const fileExists = await Promises.exists(changedFilePath); - - if (disposed) { - return; // ignore if disposed by now - } - - // File still exists, so emit as change event and reapply the watcher - if (fileExists) { - onChange('changed', changedFilePath); - - watcherDisposables = [doWatchNonRecursive(file, onChange, onError)]; - } - - // File seems to be really gone, so emit a deleted event - else { - onChange('deleted', changedFilePath); - } - }, CHANGE_BUFFER_DELAY); - - // Very important to dispose the watcher which now points to a stale inode - // and wire in a new disposable that tracks our timeout that is installed - dispose(watcherDisposables); - watcherDisposables = [toDisposable(() => clearTimeout(timeoutHandle))]; - } else { - onChange('changed', changedFilePath); - } - } - - // Folder - else { - - // Children add/delete - if (type === 'rename') { - - // Cancel any previous stats for this file path if existing - const statDisposable = mapPathToStatDisposable.get(changedFilePath); - if (statDisposable) { - dispose(statDisposable); - } - - // Wait a bit and try see if the file still exists on disk to decide on the resulting event - const timeoutHandle = setTimeout(async () => { - mapPathToStatDisposable.delete(changedFilePath); - - const fileExists = await Promises.exists(changedFilePath); - - if (disposed) { - return; // ignore if disposed by now - } - - // Figure out the correct event type: - // File Exists: either 'added' or 'changed' if known before - // File Does not Exist: always 'deleted' - let type: 'added' | 'deleted' | 'changed'; - if (fileExists) { - if (folderChildren.has(changedFileName)) { - type = 'changed'; - } else { - type = 'added'; - folderChildren.add(changedFileName); - } - } else { - folderChildren.delete(changedFileName); - type = 'deleted'; - } - - onChange(type, changedFilePath); - }, CHANGE_BUFFER_DELAY); - - mapPathToStatDisposable.set(changedFilePath, toDisposable(() => clearTimeout(timeoutHandle))); - } - - // Other events - else { - - // Figure out the correct event type: if this is the - // first time we see this child, it can only be added - let type: 'added' | 'changed'; - if (folderChildren.has(changedFileName)) { - type = 'changed'; - } else { - type = 'added'; - folderChildren.add(changedFileName); - } - - onChange(type, changedFilePath); - } - } - }); - } catch (error) { - Promises.exists(file.path).then(exists => { - if (exists && !disposed) { - onError(`Failed to watch ${file.path} for changes using fs.watch() (${error.toString()})`); - } - }); - } - - return toDisposable(() => { - disposed = true; - - watcherDisposables = dispose(watcherDisposables); - }); -} - -/** - * Watch the provided `path` for changes and return - * the data in chunks of `Uint8Array` for further use. - */ -export async function watchFileContents(path: string, onData: (chunk: Uint8Array) => void, token: CancellationToken, bufferSize = 512): Promise { - const handle = await Promises.open(path, 'r'); - const buffer = Buffer.allocUnsafe(bufferSize); - - const cts = new CancellationTokenSource(token); - - let error: Error | undefined = undefined; - let isReading = false; - - const watcher = watchFile(path, async type => { - if (type === 'changed') { - - if (isReading) { - return; // return early if we are already reading the output - } - - isReading = true; - - try { - // Consume the new contents of the file until finished - // everytime there is a change event signalling a change - while (!cts.token.isCancellationRequested) { - const { bytesRead } = await Promises.read(handle, buffer, 0, bufferSize, null); - if (!bytesRead || cts.token.isCancellationRequested) { - break; - } - - onData(buffer.slice(0, bytesRead)); - } - } catch (err) { - error = new Error(err); - cts.dispose(true); - } finally { - isReading = false; - } - } - }, err => { - error = new Error(err); - cts.dispose(true); - }); - - return new Promise((resolve, reject) => { - cts.token.onCancellationRequested(async () => { - watcher.dispose(); - await Promises.close(handle); - - if (error) { - reject(error); - } else { - resolve(); - } - }); - }); -} diff --git a/src/vs/base/test/node/extpath.test.ts b/src/vs/base/test/node/extpath.test.ts index 66b616abc9804..c4e2fd831fbed 100644 --- a/src/vs/base/test/node/extpath.test.ts +++ b/src/vs/base/test/node/extpath.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { tmpdir } from 'os'; -import { realcaseSync, realpath, realpathSync } from 'vs/base/node/extpath'; +import { realcase, realcaseSync, realpath, realpathSync } from 'vs/base/node/extpath'; import { Promises } from 'vs/base/node/pfs'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; @@ -22,7 +22,7 @@ flakySuite('Extpath', () => { return Promises.rm(testDir); }); - test('realcase', async () => { + test('realcaseSync', async () => { // assume case insensitive file system if (process.platform === 'win32' || process.platform === 'darwin') { @@ -46,6 +46,30 @@ flakySuite('Extpath', () => { } }); + test('realcase', async () => { + + // assume case insensitive file system + if (process.platform === 'win32' || process.platform === 'darwin') { + const upper = testDir.toUpperCase(); + const real = await realcase(upper); + + if (real) { // can be null in case of permission errors + assert.notStrictEqual(real, upper); + assert.strictEqual(real.toUpperCase(), upper); + assert.strictEqual(real, testDir); + } + } + + // linux, unix, etc. -> assume case sensitive file system + else { + let real = await realcase(testDir); + assert.strictEqual(real, testDir); + + real = await realcase(testDir.toUpperCase()); + assert.strictEqual(real, testDir.toUpperCase()); + } + }); + test('realpath', async () => { const realpathVal = await realpath(testDir); assert.ok(realpathVal); diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index aa53f3f2dd297..7ddab6a0522bf 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -14,7 +14,7 @@ import { randomPort } from 'vs/base/common/ports'; import { isString } from 'vs/base/common/types'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort } from 'vs/base/node/ports'; -import { watchFileContents } from 'vs/base/node/watcher'; +import { watchFileContents } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv'; import { addArg, parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper'; diff --git a/src/vs/platform/files/common/diskFileSystemProvider.ts b/src/vs/platform/files/common/diskFileSystemProvider.ts index 872d10320687f..1e0741fabe528 100644 --- a/src/vs/platform/files/common/diskFileSystemProvider.ts +++ b/src/vs/platform/files/common/diskFileSystemProvider.ts @@ -7,11 +7,11 @@ import { insert } from 'vs/base/common/arrays'; import { ThrottledDelayer } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; -import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { normalize } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { IFileChange, IWatchOptions } from 'vs/platform/files/common/files'; -import { AbstractRecursiveWatcherClient, IDiskFileChange, ILogMessage, IWatchRequest, toFileChanges } from 'vs/platform/files/common/watcher'; +import { AbstractRecursiveWatcherClient, IDiskFileChange, ILogMessage, INonRecursiveWatcher, IWatchRequest, toFileChanges } from 'vs/platform/files/common/watcher'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; export abstract class AbstractDiskFileSystemProvider extends Disposable { @@ -101,21 +101,32 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable { ): AbstractRecursiveWatcherClient; private watchNonRecursive(resource: URI, opts: IWatchOptions): IDisposable { - const watcher = this.createNonRecursiveWatcher( - this.toFilePath(resource), - opts.excludes, + const disposables = new DisposableStore(); + + const watcher = disposables.add(this.createNonRecursiveWatcher( + { + path: this.toFilePath(resource), + excludes: opts.excludes + }, changes => this._onDidChangeFile.fire(toFileChanges(changes)), msg => this.onWatcherLogMessage(msg), this.logService.getLevel() === LogLevel.Trace - ); + )); - const logLevelListener = this.logService.onDidChangeLogLevel(() => { + disposables.add(this.logService.onDidChangeLogLevel(() => { watcher.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); - }); + })); - return combinedDisposable(watcher, logLevelListener); + return disposables; } + protected abstract createNonRecursiveWatcher( + request: IWatchRequest, + onChange: (changes: IDiskFileChange[]) => void, + onLogMessage: (msg: ILogMessage) => void, + verboseLogging: boolean + ): INonRecursiveWatcher; + private onWatcherLogMessage(msg: ILogMessage): void { if (msg.type === 'error') { this._onDidWatchError.fire(msg.message); @@ -124,14 +135,6 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable { this.logService[msg.type](msg.message); } - protected abstract createNonRecursiveWatcher( - path: string, - excludes: string[], - onChange: (changes: IDiskFileChange[]) => void, - onLogMessage: (msg: ILogMessage) => void, - verboseLogging: boolean - ): IDisposable & { setVerboseLogging: (verboseLogging: boolean) => void }; - protected toFilePath(resource: URI): string { return normalize(resource.fsPath); } diff --git a/src/vs/platform/files/common/watcher.ts b/src/vs/platform/files/common/watcher.ts index 012a03fd59231..f28d0815c855d 100644 --- a/src/vs/platform/files/common/watcher.ts +++ b/src/vs/platform/files/common/watcher.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isLinux } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; import { FileChangeType, IFileChange, isParent } from 'vs/platform/files/common/files'; -export interface IRecursiveWatcher { +export interface IRecursiveWatcher extends IDisposable { /** * A normalized file change event from the raw events @@ -48,6 +48,19 @@ export interface IRecursiveWatcher { stop(): Promise; } +export interface INonRecursiveWatcher extends IDisposable { + + /** + * A promise that indicates when the watcher is ready. + */ + readonly ready: Promise; + + /** + * Enable verbose logging in the watcher. + */ + setVerboseLogging(enabled: boolean): void; +} + export abstract class AbstractRecursiveWatcherClient extends Disposable { private static readonly MAX_RESTARTS = 5; @@ -201,13 +214,9 @@ class EventCoalescer { const currentChangeType = existingEvent.type; const newChangeType = event.type; - // macOS/Windows: track renames to different case but - // same name by changing current event to DELETED - // this encodes some underlying knowledge about the - // file watcher being used by assuming we first get - // an event for the CREATE and then an event that we - // consider as DELETE if same name / different case. - if (existingEvent.path !== event.path && event.type === FileChangeType.DELETED) { + // macOS/Windows: track renames to different case + // by keeping both CREATE and DELETE events + if (existingEvent.path !== event.path && (event.type === FileChangeType.DELETED || event.type === FileChangeType.ADDED)) { keepEvent = true; } diff --git a/src/vs/platform/files/electron-main/diskFileSystemProviderServer.ts b/src/vs/platform/files/electron-main/diskFileSystemProviderServer.ts index 0fd30ea96a5a2..f44da63f2f81b 100644 --- a/src/vs/platform/files/electron-main/diskFileSystemProviderServer.ts +++ b/src/vs/platform/files/electron-main/diskFileSystemProviderServer.ts @@ -12,7 +12,7 @@ import { FileDeleteOptions, IFileChange, IWatchOptions, createFileSystemProvider import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { basename, normalize } from 'vs/base/common/path'; -import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogMessage, toFileChanges } from 'vs/platform/files/common/watcher'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { AbstractDiskFileSystemProviderChannel, ISessionFileWatcher } from 'vs/platform/files/node/diskFileSystemProviderServer'; @@ -88,8 +88,10 @@ class SessionFileWatcher extends Disposable implements ISessionFileWatcher { disposable.add(toDisposable(() => this.watcherRequests.delete(req))); const watcher = disposable.add(new NodeJSFileWatcher( - normalize(resource.fsPath), - opts.excludes, + { + path: normalize(resource.fsPath), + excludes: opts.excludes + }, changes => this.sessionEmitter.fire(toFileChanges(changes)), msg => this.onWatcherLogMessage(msg), this.logService.getLevel() === LogLevel.Trace @@ -113,7 +115,9 @@ class SessionFileWatcher extends Disposable implements ISessionFileWatcher { override dispose(): void { super.dispose(); - this.watcherRequests.forEach(disposable => dispose(disposable)); + for (const [, disposable] of this.watcherRequests) { + disposable.dispose(); + } this.watcherRequests.clear(); } } diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index ef66244e93747..d913cc4d38b11 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -23,7 +23,7 @@ import { createFileSystemProviderError, FileAtomicReadOptions, FileDeleteOptions import { readFileIntoStream } from 'vs/platform/files/common/io'; import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; import { ParcelWatcherClient } from 'vs/platform/files/node/watcher/parcel/parcelWatcherClient'; -import { AbstractRecursiveWatcherClient, IDiskFileChange, ILogMessage, IWatchRequest } from 'vs/platform/files/common/watcher'; +import { AbstractRecursiveWatcherClient, IDiskFileChange, ILogMessage, INonRecursiveWatcher, IWatchRequest } from 'vs/platform/files/common/watcher'; import { ILogService } from 'vs/platform/log/common/log'; import { AbstractDiskFileSystemProvider } from 'vs/platform/files/common/diskFileSystemProvider'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -651,15 +651,13 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple } protected createNonRecursiveWatcher( - path: string, - excludes: string[], + request: IWatchRequest, onChange: (changes: IDiskFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean - ): IDisposable & { setVerboseLogging: (verboseLogging: boolean) => void } { + ): INonRecursiveWatcher { return new NodeJSFileWatcher( - path, - excludes, + request, changes => onChange(changes), msg => onLogMessage(msg), verboseLogging diff --git a/src/vs/platform/files/node/diskFileSystemProviderServer.ts b/src/vs/platform/files/node/diskFileSystemProviderServer.ts index 9ae8f7abdd9de..5670de0fa59c8 100644 --- a/src/vs/platform/files/node/diskFileSystemProviderServer.ts +++ b/src/vs/platform/files/node/diskFileSystemProviderServer.ts @@ -234,10 +234,14 @@ export abstract class AbstractDiskFileSystemProviderChannel extends Disposabl override dispose(): void { super.dispose(); - this.watchRequests.forEach(disposable => dispose(disposable)); + for (const [, disposable] of this.watchRequests) { + disposable.dispose(); + } this.watchRequests.clear(); - this.sessionToWatcher.forEach(disposable => dispose(disposable)); + for (const [, disposable] of this.sessionToWatcher) { + disposable.dispose(); + } this.sessionToWatcher.clear(); } } diff --git a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts index 841ca4b39046f..45ae6bb505fb9 100644 --- a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts @@ -3,90 +3,306 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { watch } from 'fs'; import { ThrottledDelayer } from 'vs/base/common/async'; -import { parse, ParsedPattern } from 'vs/base/common/glob'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { basename, join } from 'vs/base/common/path'; -import { realpath } from 'vs/base/node/extpath'; -import { SymlinkSupport } from 'vs/base/node/pfs'; -import { CHANGE_BUFFER_DELAY, watchFile, watchFolder } from 'vs/base/node/watcher'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { isEqualOrParent } from 'vs/base/common/extpath'; +import { parse } from 'vs/base/common/glob'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { normalizeNFC } from 'vs/base/common/normalization'; +import { basename, dirname, join } from 'vs/base/common/path'; +import { isLinux, isMacintosh } from 'vs/base/common/platform'; +import { realcase } from 'vs/base/node/extpath'; +import { Promises } from 'vs/base/node/pfs'; import { FileChangeType } from 'vs/platform/files/common/files'; -import { IDiskFileChange, ILogMessage, coalesceEvents } from 'vs/platform/files/common/watcher'; +import { IDiskFileChange, ILogMessage, coalesceEvents, IWatchRequest, INonRecursiveWatcher } from 'vs/platform/files/common/watcher'; -export class NodeJSFileWatcher extends Disposable { +export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatcher { - private readonly fileChangesDelayer: ThrottledDelayer = this._register(new ThrottledDelayer(CHANGE_BUFFER_DELAY * 2 /* sync on delay from underlying library */)); + // A delay in reacting to file deletes to support + // atomic save operations where a tool may chose + // to delete a file before creating it again for + // an update. + private static readonly FILE_DELETE_HANDLER_DELAY = 25; + + // A delay for collecting file changes from node.js + // before collecting them for coalescing and emitting + // (same delay as Parcel is using) + private static readonly FILE_CHANGES_HANDLER_DELAY = 50; + + private readonly fileChangesDelayer = this._register(new ThrottledDelayer(NodeJSFileWatcher.FILE_CHANGES_HANDLER_DELAY)); private fileChangesBuffer: IDiskFileChange[] = []; - private isDisposed: boolean | undefined; - private readonly excludePatterns = this.excludes.map(exclude => parse(exclude)); + private readonly excludes = this.request.excludes.map(exclude => parse(exclude)); + + private readonly cts = new CancellationTokenSource(); + + readonly ready = this.watch(); constructor( - private path: string, - private excludes: string[], + private request: IWatchRequest, private onDidFilesChange: (changes: IDiskFileChange[]) => void, - private onLogMessage: (msg: ILogMessage) => void, - private verboseLogging: boolean + private onLogMessage?: (msg: ILogMessage) => void, + private verboseLogging?: boolean ) { super(); - - this.startWatching(); } - setVerboseLogging(verboseLogging: boolean): void { - this.verboseLogging = verboseLogging; + private async watch(): Promise { + try { + const realPath = await this.normalizePath(this.request); + + if (this.cts.token.isCancellationRequested) { + return; + } + + this.trace(`Request to start watching: ${realPath} (excludes: ${this.request.excludes}))}`); + + // Watch via node.js + const stat = await Promises.stat(realPath); + this._register(await this.doWatch(realPath, stat.isDirectory())); + + } catch (error) { + if (error.code !== 'ENOENT') { + this.error(error); + } + } } - private async startWatching(): Promise { + private async normalizePath(request: IWatchRequest): Promise { + let realPath = request.path; + try { - const { stat, symbolicLink } = await SymlinkSupport.stat(this.path); - if (this.isDisposed) { - return; + // First check for symbolic link + realPath = await Promises.realpath(request.path); + + // Second check for casing difference + // Note: this will be a no-op on Linux platforms + if (request.path === realPath) { + realPath = await realcase(request.path) ?? request.path; } - let pathToWatch = this.path; - if (symbolicLink) { + // Correct watch path as needed + if (request.path !== realPath) { + this.warn(`correcting a path to watch that seems to be a symbolic link or wrong casing (original: ${request.path}, real: ${realPath})`); + } + } catch (error) { + // ignore + } + + return realPath; + } + + private async doWatch(path: string, isDirectory: boolean): Promise { + + // macOS: watching samba shares can crash VSCode so we do + // a simple check for the file path pointing to /Volumes + // (https://github.com/microsoft/vscode/issues/106879) + // TODO@electron this needs a revisit when the crash is + // fixed or mitigated upstream. + if (isMacintosh && isEqualOrParent(path, '/Volumes/')) { + this.error(`Refusing to watch ${path} for changes using fs.watch() for possibly being a network share where watching is unreliable and unstable.`); + + return Disposable.None; + } + + const cts = new CancellationTokenSource(this.cts.token); + + let disposables = new DisposableStore(); + + try { + + // Creating watcher can fail with an exception + const watcher = watch(path); + disposables.add(toDisposable(() => { + watcher.removeAllListeners(); + watcher.close(); + })); + + // Folder: resolve children to emit proper events + const folderChildren = new Set(); + if (isDirectory) { try { - pathToWatch = await realpath(pathToWatch); + for (const child of await Promises.readdir(path)) { + folderChildren.add(child); + } } catch (error) { this.error(error); + } + } + + const mapPathToStatDisposable = new Map(); + disposables.add(toDisposable(() => { + for (const [, disposable] of mapPathToStatDisposable) { + disposable.dispose(); + } + mapPathToStatDisposable.clear(); + })); + + watcher.on('error', (code: number, signal: string) => { + this.error(`Failed to watch ${path} for changes using fs.watch() (${code}, ${signal})`); + }); - if (symbolicLink.dangling) { - return; // give up if symbolic link is dangling + watcher.on('change', (type, raw) => { + if (cts.token.isCancellationRequested) { + return; // ignore if already disposed + } + + this.trace(`["${type}"] ${raw} (fs.watch() raw event)`); + + // Normalize file name + let changedFileName = ''; + if (raw) { // https://github.com/microsoft/vscode/issues/38191 + changedFileName = raw.toString(); + if (isMacintosh) { + // Mac: uses NFD unicode form on disk, but we want NFC + // See also https://github.com/nodejs/node/issues/2165 + changedFileName = normalizeNFC(changedFileName); } } - } - this.trace(`Request to start watching: ${pathToWatch} (excludes: ${this.excludes}))}`); + if (!changedFileName || (type !== 'change' && type !== 'rename')) { + return; // ignore unexpected events + } - // Watch Folder - if (stat.isDirectory()) { - this._register(watchFolder(pathToWatch, (eventType, path) => { - this.onFileChange({ - type: eventType === 'changed' ? FileChangeType.UPDATED : eventType === 'added' ? FileChangeType.ADDED : FileChangeType.DELETED, - path: join(this.path, basename(path)) // ensure path is identical with what was passed in - }); - }, error => this.error(error))); - } + // File + if (!isDirectory) { + if (type === 'rename' || changedFileName !== basename(path)) { - // Watch File - else { - this._register(watchFile(pathToWatch, eventType => { - this.onFileChange({ - type: eventType === 'changed' ? FileChangeType.UPDATED : FileChangeType.DELETED, - path: this.path // ensure path is identical with what was passed in - }); - }, error => this.error(error))); - } + // The file was either deleted or renamed. Many tools apply changes to files in an + // atomic way ("Atomic Save") by first renaming the file to a temporary name and then + // renaming it back to the original name. Our watcher will detect this as a rename + // and then stops to work on Mac and Linux because the watcher is applied to the + // inode and not the name. The fix is to detect this case and trying to watch the file + // again after a certain delay. + // In addition, we send out a delete event if after a timeout we detect that the file + // does indeed not exist anymore. + + const timeoutHandle = setTimeout(async () => { + const fileExists = await Promises.exists(path); + + if (cts.token.isCancellationRequested) { + return; // ignore if disposed by now + } + + // File still exists, so emit as change event and reapply the watcher + if (fileExists) { + this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }); + + disposables.add(await this.doWatch(path, false)); + } + + // File seems to be really gone, so emit a deleted event + else { + this.onFileChange({ path: this.request.path, type: FileChangeType.DELETED }); + } + }, NodeJSFileWatcher.FILE_DELETE_HANDLER_DELAY); + + // Very important to dispose the watcher which now points to a stale inode + // and wire in a new disposable that tracks our timeout that is installed + disposables.clear(); + disposables.add(toDisposable(() => clearTimeout(timeoutHandle))); + } else { + this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }); + } + } + + // Folder + else { + + // Children add/delete + if (type === 'rename') { + + // Cancel any previous stats for this file if existing + mapPathToStatDisposable.get(changedFileName)?.dispose(); + + // Wait a bit and try see if the file still exists on disk + // to decide on the resulting event + const timeoutHandle = setTimeout(async () => { + mapPathToStatDisposable.delete(changedFileName); + + // fs.watch() does not really help us figuring out + // if the root folder got deleted. As such we have + // to check if our watched path still exists and + // handle that accordingly. + // + // We do not re-attach the watcher after timeout + // though as we do for file watches because for + // file watching specifically we want to handle + // the atomic-write cases. + if (!await Promises.exists(path)) { + this.onFileChange({ path: this.request.path, type: FileChangeType.DELETED }); + } + + else { + + // In order to properly detect renames on a case-insensitive + // file system, we need to use `existsChildStrictCase` helper + // because otherwise we would wrongly assume a file exists + // when it was renamed in the old form. + const fileExists = await this.existsChildStrictCase(join(path, changedFileName)); + + if (cts.token.isCancellationRequested) { + return; // ignore if disposed by now + } + + // Figure out the correct event type: + // File Exists: either 'added' or 'updated' if known before + // File Does not Exist: always 'deleted' + let type: FileChangeType; + if (fileExists) { + if (folderChildren.has(changedFileName)) { + type = FileChangeType.UPDATED; + } else { + type = FileChangeType.ADDED; + folderChildren.add(changedFileName); + } + } else { + folderChildren.delete(changedFileName); + type = FileChangeType.DELETED; + } + + this.onFileChange({ path: join(this.request.path, changedFileName), type }); + } + }, NodeJSFileWatcher.FILE_DELETE_HANDLER_DELAY); + + mapPathToStatDisposable.set(changedFileName, toDisposable(() => clearTimeout(timeoutHandle))); + } + + // Other events + else { + + // Figure out the correct event type: if this is the + // first time we see this child, it can only be added + let type: FileChangeType; + if (folderChildren.has(changedFileName)) { + type = FileChangeType.UPDATED; + } else { + type = FileChangeType.ADDED; + folderChildren.add(changedFileName); + } + + this.onFileChange({ path: join(this.request.path, changedFileName), type }); + } + } + }); } catch (error) { - if (error.code !== 'ENOENT') { - this.error(error); + if (await Promises.exists(path) && !cts.token.isCancellationRequested) { + this.error(`Failed to watch ${path} for changes using fs.watch() (${error.toString()})`); } } + + return toDisposable(() => { + cts.dispose(true); + disposables.dispose(); + }); } private onFileChange(event: IDiskFileChange): void { + if (this.cts.token.isCancellationRequested) { + return; + } // Logging if (this.verboseLogging) { @@ -94,12 +310,12 @@ export class NodeJSFileWatcher extends Disposable { } // Add to buffer unless ignored - if (!this.isPathIgnored(event.path, this.excludePatterns)) { - this.fileChangesBuffer.push(event); - } else { + if (this.excludes.some(exclude => exclude(event.path))) { if (this.verboseLogging) { this.trace(` >> ignored ${event.path}`); } + } else { + this.fileChangesBuffer.push(event); } // Handle emit through delayer to accommodate for bulk changes and thus reduce spam @@ -107,7 +323,7 @@ export class NodeJSFileWatcher extends Disposable { const fileChanges = this.fileChangesBuffer; this.fileChangesBuffer = []; - // Event coalsecer + // Coalesce events: merge events of same kind const coalescedFileChanges = coalesceEvents(fileChanges); // Logging @@ -117,32 +333,119 @@ export class NodeJSFileWatcher extends Disposable { } } - // Fire + // Broadcast to clients if (coalescedFileChanges.length > 0) { this.onDidFilesChange(coalescedFileChanges); } + }).catch(() => { + // ignore (we are likely disposed and cancelled) }); } - private isPathIgnored(absolutePath: string, ignored: ParsedPattern[]): boolean { - return ignored.some(ignore => ignore(absolutePath)); + private async existsChildStrictCase(path: string): Promise { + if (isLinux) { + return await Promises.exists(path); + } + + try { + const children = await Promises.readdir(dirname(path)); + return children.some(child => child === basename(path)); + } catch { + return false; + } + } + + setVerboseLogging(verboseLogging: boolean): void { + this.verboseLogging = verboseLogging; } private error(error: string): void { - if (!this.isDisposed) { - this.onLogMessage({ type: 'error', message: `[File Watcher (node.js)] ${error}` }); + if (!this.cts.token.isCancellationRequested) { + this.onLogMessage?.({ type: 'error', message: `[File Watcher (node.js)] ${error}` }); + } + } + + private warn(message: string): void { + if (!this.cts.token.isCancellationRequested) { + this.onLogMessage?.({ type: 'warn', message: `[File Watcher (node.js)] ${message}` }); } } private trace(message: string): void { - if (!this.isDisposed && this.verboseLogging) { - this.onLogMessage({ type: 'trace', message: `[File Watcher (node.js)] ${message}` }); + if (!this.cts.token.isCancellationRequested && this.verboseLogging) { + this.onLogMessage?.({ type: 'trace', message: `[File Watcher (node.js)] ${message}` }); } } override dispose(): void { - this.isDisposed = true; + this.cts.dispose(true); super.dispose(); } } + +/** + * Watch the provided `path` for changes and return + * the data in chunks of `Uint8Array` for further use. + */ +export async function watchFileContents(path: string, onData: (chunk: Uint8Array) => void, token: CancellationToken, bufferSize = 512): Promise { + const handle = await Promises.open(path, 'r'); + const buffer = Buffer.allocUnsafe(bufferSize); + + const cts = new CancellationTokenSource(token); + + let error: Error | undefined = undefined; + let isReading = false; + + const request: IWatchRequest = { path, excludes: [] }; + const watcher = new NodeJSFileWatcher(request, changes => { + (async () => { + for (const { type } of changes) { + if (type === FileChangeType.UPDATED) { + + if (isReading) { + return; // return early if we are already reading the output + } + + isReading = true; + + try { + // Consume the new contents of the file until finished + // everytime there is a change event signalling a change + while (!cts.token.isCancellationRequested) { + const { bytesRead } = await Promises.read(handle, buffer, 0, bufferSize, null); + if (!bytesRead || cts.token.isCancellationRequested) { + break; + } + + onData(buffer.slice(0, bytesRead)); + } + } catch (err) { + error = new Error(err); + cts.dispose(true); + } finally { + isReading = false; + } + } + } + })(); + }); + + return new Promise((resolve, reject) => { + cts.token.onCancellationRequested(async () => { + watcher.dispose(); + + try { + await Promises.close(handle); + } catch (err) { + error = new Error(err); + } + + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); +} diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index 83678d3ca3a5e..158315cb28040 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -19,7 +19,7 @@ import { dirname, isAbsolute, join, normalize, sep } from 'vs/base/common/path'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { rtrim } from 'vs/base/common/strings'; import { realcaseSync, realpathSync } from 'vs/base/node/extpath'; -import { watchFolder } from 'vs/base/node/watcher'; +import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; import { FileChangeType } from 'vs/platform/files/common/files'; import { IDiskFileChange, ILogMessage, coalesceEvents, IWatchRequest, IRecursiveWatcher } from 'vs/platform/files/common/watcher'; @@ -397,12 +397,12 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { this.trace(`${type === FileChangeType.ADDED ? '[ADDED]' : type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${path}`); } - if (!this.isPathIgnored(path, excludes)) { - events.push({ type, path }); - } else { + if (excludes.some(exclude => exclude(path))) { if (this.verboseLogging) { this.trace(` >> ignored ${path}`); } + } else { + events.push({ type, path }); } } @@ -508,27 +508,29 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { const parentPath = dirname(watcher.request.path); if (existsSync(parentPath)) { - const disposable = watchFolder(parentPath, (type, path) => { + const nodeWatcher = new NodeJSFileWatcher({ path: parentPath, excludes: [] }, changes => { if (watcher.token.isCancellationRequested) { return; // return early when disposed } // Watcher path came back! Restart watching... - if (path === watcher.request.path && (type === 'added' || type === 'changed')) { - this.warn('Watcher restarts because watched path got created again', watcher); + for (const { path, type } of changes) { + if (path === watcher.request.path && (type === FileChangeType.ADDED || type === FileChangeType.UPDATED)) { + this.warn('Watcher restarts because watched path got created again', watcher); - // Stop watching that parent folder - disposable.dispose(); + // Stop watching that parent folder + nodeWatcher.dispose(); - // Restart the file watching - this.restartWatching(watcher); + // Restart the file watching + this.restartWatching(watcher); + + break; + } } - }, error => { - // Ignore - }); + }, msg => this._onDidLogMessage.fire(msg), this.verboseLogging); // Make sure to stop watching when the watcher is disposed - watcher.token.onCancellationRequested(() => disposable.dispose()); + watcher.token.onCancellationRequested(() => nodeWatcher.dispose()); } } @@ -640,10 +642,6 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { return Array.from(requestTrie).map(([, request]) => request); } - private isPathIgnored(absolutePath: string, ignored: ParsedPattern[]): boolean { - return ignored.some(ignore => ignore(absolutePath)); - } - async setVerboseLogging(enabled: boolean): Promise { this.verboseLogging = enabled; } diff --git a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts new file mode 100644 index 0000000000000..c5c8fbfdae7c8 --- /dev/null +++ b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts @@ -0,0 +1,435 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { tmpdir } from 'os'; +import { basename, dirname, join } from 'vs/base/common/path'; +import { Promises, RimRafMode } from 'vs/base/node/pfs'; +import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { FileChangeType } from 'vs/platform/files/common/files'; +import { IDiskFileChange } from 'vs/platform/files/common/watcher'; +import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; +import { timeout } from 'vs/base/common/async'; +import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; + +// this suite has shown flaky runs in Azure pipelines where +// tasks would just hang and timeout after a while (not in +// mocha but generally). as such they will run only on demand +// whenever we update the watcher library. + +((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('Recursive File Watcher (node.js)', () => { + + let testDir: string; + let watcher: NodeJSFileWatcher; + let event: Event; + + let loggingEnabled = false; + + function enableLogging(enable: boolean) { + loggingEnabled = enable; + watcher?.setVerboseLogging(enable); + } + + enableLogging(false); + + setup(async function () { + testDir = getRandomTestPath(tmpdir(), 'vsctests', 'filewatcher'); + + const sourceDir = getPathFromAmdModule(require, './fixtures/service'); + + await Promises.copy(sourceDir, testDir, { preserveSymlinks: false }); + + await createWatcher(testDir); + }); + + function createWatcher(path: string): Promise { + if (watcher) { + watcher.dispose(); + } + + const emitter = new Emitter(); + event = emitter.event; + + watcher = new NodeJSFileWatcher({ path, excludes: [] }, changes => emitter.fire(changes), msg => { + if (loggingEnabled) { + console.log(`[recursive watcher test message] ${msg.type}: ${msg.message}`); + } + }, loggingEnabled); + + return watcher.ready; + } + + teardown(async () => { + watcher.dispose(); + + // Possible that the file watcher is still holding + // onto the folders on Windows specifically and the + // unlink would fail. In that case, do not fail the + // test suite. + return Promises.rm(testDir).catch(error => console.error(error)); + }); + + async function awaitEvent(onDidChangeFile: Event, path: string, type: FileChangeType, failOnEventReason?: string): Promise { + if (loggingEnabled) { + console.log(`Awaiting change type '${toMsg(type)}' on file '${path}'`); + } + + // Await the event + await new Promise((resolve, reject) => { + const disposable = onDidChangeFile(events => { + for (const event of events) { + if (event.path === path && event.type === type) { + disposable.dispose(); + if (failOnEventReason) { + reject(new Error(`Unexpected file event: ${failOnEventReason}`)); + } else { + setImmediate(() => resolve()); // copied from parcel watcher tests, seems to drop unrelated events on macOS + } + break; + } + } + }); + }); + } + + function toMsg(type: FileChangeType): string { + switch (type) { + case FileChangeType.ADDED: return 'added'; + case FileChangeType.DELETED: return 'deleted'; + default: return 'changed'; + } + } + + test('basics (folder watch)', async function () { + + // New file + const newFilePath = join(testDir, 'newFile.txt'); + let changeFuture: Promise = awaitEvent(event, newFilePath, FileChangeType.ADDED); + await Promises.writeFile(newFilePath, 'Hello World'); + await changeFuture; + + // New folder + const newFolderPath = join(testDir, 'New Folder'); + changeFuture = awaitEvent(event, newFolderPath, FileChangeType.ADDED); + await Promises.mkdir(newFolderPath); + await changeFuture; + + // Rename file + let renamedFilePath = join(testDir, 'renamedFile.txt'); + changeFuture = Promise.all([ + awaitEvent(event, newFilePath, FileChangeType.DELETED), + awaitEvent(event, renamedFilePath, FileChangeType.ADDED) + ]); + await Promises.rename(newFilePath, renamedFilePath); + await changeFuture; + + // Rename folder + let renamedFolderPath = join(testDir, 'Renamed Folder'); + changeFuture = Promise.all([ + awaitEvent(event, newFolderPath, FileChangeType.DELETED), + awaitEvent(event, renamedFolderPath, FileChangeType.ADDED) + ]); + await Promises.rename(newFolderPath, renamedFolderPath); + await changeFuture; + + // Rename file (same name, different case) + const caseRenamedFilePath = join(testDir, 'RenamedFile.txt'); + changeFuture = Promise.all([ + awaitEvent(event, renamedFilePath, FileChangeType.DELETED), + awaitEvent(event, caseRenamedFilePath, FileChangeType.ADDED) + ]); + await Promises.rename(renamedFilePath, caseRenamedFilePath); + await changeFuture; + renamedFilePath = caseRenamedFilePath; + + // Rename folder (same name, different case) + const caseRenamedFolderPath = join(testDir, 'REnamed Folder'); + changeFuture = Promise.all([ + awaitEvent(event, renamedFolderPath, FileChangeType.DELETED), + awaitEvent(event, caseRenamedFolderPath, FileChangeType.ADDED) + ]); + await Promises.rename(renamedFolderPath, caseRenamedFolderPath); + await changeFuture; + renamedFolderPath = caseRenamedFolderPath; + + // Move file + const movedFilepath = join(testDir, 'movedFile.txt'); + changeFuture = Promise.all([ + awaitEvent(event, renamedFilePath, FileChangeType.DELETED), + awaitEvent(event, movedFilepath, FileChangeType.ADDED) + ]); + await Promises.rename(renamedFilePath, movedFilepath); + await changeFuture; + + // Move folder + const movedFolderpath = join(testDir, 'Moved Folder'); + changeFuture = Promise.all([ + awaitEvent(event, renamedFolderPath, FileChangeType.DELETED), + awaitEvent(event, movedFolderpath, FileChangeType.ADDED) + ]); + await Promises.rename(renamedFolderPath, movedFolderpath); + await changeFuture; + + // Copy file + const copiedFilepath = join(testDir, 'copiedFile.txt'); + changeFuture = awaitEvent(event, copiedFilepath, FileChangeType.ADDED); + await Promises.copyFile(movedFilepath, copiedFilepath); + await changeFuture; + + // Copy folder + const copiedFolderpath = join(testDir, 'Copied Folder'); + changeFuture = awaitEvent(event, copiedFolderpath, FileChangeType.ADDED); + await Promises.copy(movedFolderpath, copiedFolderpath, { preserveSymlinks: false }); + await changeFuture; + + // Change file + changeFuture = awaitEvent(event, copiedFilepath, FileChangeType.UPDATED); + await Promises.writeFile(copiedFilepath, 'Hello Change'); + await changeFuture; + + // Create new file + const anotherNewFilePath = join(testDir, 'anotherNewFile.txt'); + changeFuture = awaitEvent(event, anotherNewFilePath, FileChangeType.ADDED); + await Promises.writeFile(anotherNewFilePath, 'Hello Another World'); + await changeFuture; + + // Read file does not emit event + changeFuture = awaitEvent(event, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-read-file'); + await Promises.readFile(anotherNewFilePath); + await Promise.race([timeout(100), changeFuture]); + + // Stat file does not emit event + changeFuture = awaitEvent(event, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); + await Promises.stat(anotherNewFilePath); + await Promise.race([timeout(100), changeFuture]); + + // Stat folder does not emit event + changeFuture = awaitEvent(event, copiedFolderpath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); + await Promises.stat(copiedFolderpath); + await Promise.race([timeout(100), changeFuture]); + + // Delete file + changeFuture = awaitEvent(event, copiedFilepath, FileChangeType.DELETED); + await Promises.unlink(copiedFilepath); + await changeFuture; + + // Delete folder + changeFuture = awaitEvent(event, copiedFolderpath, FileChangeType.DELETED); + await Promises.rmdir(copiedFolderpath); + await changeFuture; + + watcher.dispose(); + }); + + test('basics (file watch)', async function () { + const filePath = join(testDir, 'lorem.txt'); + await createWatcher(filePath); + + // Change file + let changeFuture = awaitEvent(event, filePath, FileChangeType.UPDATED); + await Promises.writeFile(filePath, 'Hello Change'); + await changeFuture; + + // Delete file + changeFuture = awaitEvent(event, filePath, FileChangeType.DELETED); + await Promises.unlink(filePath); + await changeFuture; + + // Recreate watcher + await Promises.writeFile(filePath, 'Hello Change'); + await createWatcher(filePath); + + // Move file + changeFuture = awaitEvent(event, filePath, FileChangeType.DELETED); + await Promises.move(filePath, `${filePath}-moved`); + await changeFuture; + }); + + test('atomic writes (folder watch)', async function () { + + // Delete + Recreate file + const newFilePath = join(testDir, 'lorem.txt'); + let changeFuture: Promise = awaitEvent(event, newFilePath, FileChangeType.UPDATED); + await Promises.unlink(newFilePath); + Promises.writeFile(newFilePath, 'Hello Atomic World'); + await changeFuture; + }); + + test('atomic writes (file watch)', async function () { + const filePath = join(testDir, 'lorem.txt'); + await createWatcher(filePath); + + // Delete + Recreate file + const newFilePath = join(filePath); + let changeFuture: Promise = awaitEvent(event, newFilePath, FileChangeType.UPDATED); + await Promises.unlink(newFilePath); + Promises.writeFile(newFilePath, 'Hello Atomic World'); + await changeFuture; + }); + + test('multiple events (folder watch)', async function () { + + // multiple add + + const newFilePath1 = join(testDir, 'newFile-1.txt'); + const newFilePath2 = join(testDir, 'newFile-2.txt'); + const newFilePath3 = join(testDir, 'newFile-3.txt'); + + const addedFuture1: Promise = awaitEvent(event, newFilePath1, FileChangeType.ADDED); + const addedFuture2: Promise = awaitEvent(event, newFilePath2, FileChangeType.ADDED); + const addedFuture3: Promise = awaitEvent(event, newFilePath3, FileChangeType.ADDED); + + await Promise.all([ + await Promises.writeFile(newFilePath1, 'Hello World 1'), + await Promises.writeFile(newFilePath2, 'Hello World 2'), + await Promises.writeFile(newFilePath3, 'Hello World 3'), + ]); + + await Promise.all([addedFuture1, addedFuture2, addedFuture3]); + + // multiple change + + const changeFuture1: Promise = awaitEvent(event, newFilePath1, FileChangeType.UPDATED); + const changeFuture2: Promise = awaitEvent(event, newFilePath2, FileChangeType.UPDATED); + const changeFuture3: Promise = awaitEvent(event, newFilePath3, FileChangeType.UPDATED); + + await Promise.all([ + await Promises.writeFile(newFilePath1, 'Hello Update 1'), + await Promises.writeFile(newFilePath2, 'Hello Update 2'), + await Promises.writeFile(newFilePath3, 'Hello Update 3'), + ]); + + await Promise.all([changeFuture1, changeFuture2, changeFuture3]); + + // copy with multiple files + + const copyFuture1: Promise = awaitEvent(event, join(testDir, 'newFile-1-copy.txt'), FileChangeType.ADDED); + const copyFuture2: Promise = awaitEvent(event, join(testDir, 'newFile-2-copy.txt'), FileChangeType.ADDED); + const copyFuture3: Promise = awaitEvent(event, join(testDir, 'newFile-3-copy.txt'), FileChangeType.ADDED); + + await Promise.all([ + Promises.copy(join(testDir, 'newFile-1.txt'), join(testDir, 'newFile-1-copy.txt'), { preserveSymlinks: false }), + Promises.copy(join(testDir, 'newFile-2.txt'), join(testDir, 'newFile-2-copy.txt'), { preserveSymlinks: false }), + Promises.copy(join(testDir, 'newFile-3.txt'), join(testDir, 'newFile-3-copy.txt'), { preserveSymlinks: false }) + ]); + + await Promise.all([copyFuture1, copyFuture2, copyFuture3]); + + // multiple delete + + const deleteFuture1: Promise = awaitEvent(event, newFilePath1, FileChangeType.DELETED); + const deleteFuture2: Promise = awaitEvent(event, newFilePath2, FileChangeType.DELETED); + const deleteFuture3: Promise = awaitEvent(event, newFilePath3, FileChangeType.DELETED); + + await Promise.all([ + await Promises.unlink(newFilePath1), + await Promises.unlink(newFilePath2), + await Promises.unlink(newFilePath3) + ]); + + await Promise.all([deleteFuture1, deleteFuture2, deleteFuture3]); + }); + + test('multiple events (file watch)', async function () { + const filePath = join(testDir, 'lorem.txt'); + await createWatcher(filePath); + + // multiple change + + const changeFuture1: Promise = awaitEvent(event, filePath, FileChangeType.UPDATED); + + await Promise.all([ + await Promises.writeFile(filePath, 'Hello Update 1'), + await Promises.writeFile(filePath, 'Hello Update 2'), + await Promises.writeFile(filePath, 'Hello Update 3'), + ]); + + await Promise.all([changeFuture1]); + }); + + (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (folder watch)', async function () { + const link = join(testDir, 'deep-linked'); + const linkTarget = join(testDir, 'deep'); + await Promises.symlink(linkTarget, link); + + await createWatcher(link); + + // New file + const newFilePath = join(link, 'newFile.txt'); + let changeFuture: Promise = awaitEvent(event, newFilePath, FileChangeType.ADDED); + await Promises.writeFile(newFilePath, 'Hello World'); + await changeFuture; + }); + + (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (file watch)', async function () { + const link = join(testDir, 'lorem.txt-linked'); + const linkTarget = join(testDir, 'lorem.txt'); + await Promises.symlink(linkTarget, link); + + await createWatcher(link); + + // Change file + let changeFuture = awaitEvent(event, link, FileChangeType.UPDATED); + await Promises.writeFile(link, 'Hello Change'); + await changeFuture; + + // Delete file + changeFuture = awaitEvent(event, link, FileChangeType.DELETED); + await Promises.unlink(linkTarget); + await changeFuture; + }); + + (isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing (folder watch)', async function () { + const wrongCase = join(dirname(testDir), basename(testDir).toUpperCase()); + await createWatcher(wrongCase); + + // New file + const newFilePath = join(wrongCase, 'newFile.txt'); + let changeFuture: Promise = awaitEvent(event, newFilePath, FileChangeType.ADDED); + await Promises.writeFile(newFilePath, 'Hello World'); + await changeFuture; + }); + + (isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing (file watch)', async function () { + const filePath = join(testDir, 'LOREM.txt'); + await createWatcher(filePath); + + // Change file + let changeFuture = awaitEvent(event, filePath, FileChangeType.UPDATED); + await Promises.writeFile(filePath, 'Hello Change'); + await changeFuture; + + // Delete file + changeFuture = awaitEvent(event, filePath, FileChangeType.DELETED); + await Promises.unlink(filePath); + await changeFuture; + }); + + test('invalid path does not explode', async function () { + const invalidPath = join(testDir, 'invalid'); + + await createWatcher(invalidPath); + }); + + (isMacintosh /* macOS: does not seem to report this */ ? test.skip : test)('deleting watched path is handled properly (folder watch)', async function () { + const watchedPath = join(testDir, 'deep'); + await createWatcher(watchedPath); + + // Delete watched path + const changeFuture = awaitEvent(event, watchedPath, FileChangeType.DELETED); + Promises.rm(watchedPath, RimRafMode.UNLINK); + await changeFuture; + }); + + test('deleting watched path is handled properly (file watch)', async function () { + const watchedPath = join(testDir, 'lorem.txt'); + await createWatcher(watchedPath); + + // Delete watched path + const changeFuture = awaitEvent(event, watchedPath, FileChangeType.DELETED); + Promises.unlink(watchedPath); + await changeFuture; + }); +}); diff --git a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts index d1ec83d19f209..777152eb95000 100644 --- a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts @@ -20,7 +20,7 @@ import { IWatchRequest } from 'vs/platform/files/common/watcher'; // mocha but generally). as such they will run only on demand // whenever we update the watcher library. -((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('Recursive Watcher (parcel)', () => { +((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('Recursive File Watcher (parcel)', () => { class TestParcelWatcher extends ParcelWatcher { @@ -491,6 +491,7 @@ import { IWatchRequest } from 'vs/platform/files/common/watcher'; await warnFuture; // Restore watched path + await timeout(1500); // node.js watcher used for monitoring folder restore is async await Promises.mkdir(watchedPath); await timeout(1500); // restart is delayed await watcher.whenReady(); diff --git a/src/vs/server/remoteFileSystemProviderServer.ts b/src/vs/server/remoteFileSystemProviderServer.ts index ee129f2e27bda..c4a4d33f7f980 100644 --- a/src/vs/server/remoteFileSystemProviderServer.ts +++ b/src/vs/server/remoteFileSystemProviderServer.ts @@ -122,7 +122,9 @@ class SessionFileWatcher extends Disposable implements ISessionFileWatcher { override dispose(): void { super.dispose(); - this.watcherRequests.forEach(disposable => dispose(disposable)); + for (const [, disposable] of this.watcherRequests) { + disposable.dispose(); + } this.watcherRequests.clear(); } } diff --git a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts index 73697b42976f0..a54c88b84e52b 100644 --- a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts @@ -170,7 +170,9 @@ export class WorkspaceWatcher extends Disposable { } private unwatchWorkspaces(): void { - this.watchedWorkspaces.forEach(disposable => dispose(disposable)); + for (const [, disposable] of this.watchedWorkspaces) { + disposable.dispose(); + } this.watchedWorkspaces.clear(); } From a1541e92a2ee4f86e2aea1fcd0079b6b8ab5f821 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Jan 2022 09:18:33 +0100 Subject: [PATCH 0964/2210] watcher - fix `normalizeRequests` to take case sensitivty into account --- src/vs/base/common/map.ts | 4 ++-- src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 7fe1402cdafd4..97370fd6f2c2c 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -332,8 +332,8 @@ export class TernarySearchTree { return new TernarySearchTree(new UriIterator(ignorePathCasing)); } - static forPaths(): TernarySearchTree { - return new TernarySearchTree(new PathIterator()); + static forPaths(ignorePathCasing = false): TernarySearchTree { + return new TernarySearchTree(new PathIterator(undefined, !ignorePathCasing)); } static forStrings(): TernarySearchTree { diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index 158315cb28040..36bec5c087fc8 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -608,7 +608,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { } protected normalizeRequests(requests: IWatchRequest[]): IWatchRequest[] { - const requestTrie = TernarySearchTree.forPaths(); + const requestTrie = TernarySearchTree.forPaths(!isLinux); // Sort requests by path length to have shortest first // to have a way to prevent children to be watched if From 1c68771543f05037be2cd8843e5e1a48e22c046b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Jan 2022 09:25:59 +0100 Subject: [PATCH 0965/2210] `rewriteAbsolutePaths` should also check for urls, https://github.com/microsoft/vscode/issues/111211 --- src/vs/platform/profiling/common/profiling.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/profiling/common/profiling.ts b/src/vs/platform/profiling/common/profiling.ts index ddd6d1a294915..99f99522f35f1 100644 --- a/src/vs/platform/profiling/common/profiling.ts +++ b/src/vs/platform/profiling/common/profiling.ts @@ -48,7 +48,7 @@ export namespace Utils { export function rewriteAbsolutePaths(profile: IV8Profile, replace: string = 'noAbsolutePaths') { for (const node of profile.nodes) { if (node.callFrame && node.callFrame.url) { - if (isAbsolute(node.callFrame.url)) { + if (isAbsolute(node.callFrame.url) || /^\w[\w\d+.-]*:\/\/\//.test(node.callFrame.url)) { node.callFrame.url = join(replace, basename(node.callFrame.url)); } } From 5a042ad34f39376c74bf6173edcdc6fb2ab33296 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Jan 2022 09:35:16 +0100 Subject: [PATCH 0966/2210] adopt latest v8-inspect-profiler, use own utils and just write file, https://github.com/microsoft/vscode/issues/111211 --- package.json | 4 ++-- src/vs/code/node/cli.ts | 7 ++++--- yarn.lock | 10 +++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ae8c1ba892e08..da691d1e32e78 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "node-pty": "0.11.0-beta11", "spdlog": "^0.13.0", "tas-client-umd": "0.1.4", - "v8-inspect-profiler": "^0.0.22", + "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.6.1", "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", @@ -228,4 +228,4 @@ "elliptic": "^6.5.3", "nwmatcher": "^1.4.4" } -} \ No newline at end of file +} diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 7ddab6a0522bf..a8be84cfce27d 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -23,6 +23,7 @@ import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; import product from 'vs/platform/product/common/product'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { randomPath } from 'vs/base/common/extpath'; +import { Utils } from 'vs/platform/profiling/common/profiling'; function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { return !!argv['install-source'] @@ -285,17 +286,17 @@ export async function main(argv: string[]): Promise { return; } let suffix = ''; - let profile = await session.stop(); + let result = await session.stop(); if (!process.env['VSCODE_DEV']) { // when running from a not-development-build we remove // absolute filenames because we don't want to reveal anything // about users. We also append the `.txt` suffix to make it // easier to attach these files to GH issues - profile = profiler.rewriteAbsolutePaths(profile, 'piiRemoved'); + result.profile = Utils.rewriteAbsolutePaths(result.profile, 'piiRemoved'); suffix = '.txt'; } - await profiler.writeProfile(profile, `${filenamePrefix}.${name}.cpuprofile${suffix}`); + writeFileSync(`${filenamePrefix}.${name}.cpuprofile${suffix}`, JSON.stringify(result.profile, undefined, 4)); } }; } diff --git a/yarn.lock b/yarn.lock index d48913f1b1eac..a89d807a3e26a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10231,10 +10231,10 @@ v8-compile-cache@^2.2.0: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -v8-inspect-profiler@^0.0.22: - version "0.0.22" - resolved "https://registry.yarnpkg.com/v8-inspect-profiler/-/v8-inspect-profiler-0.0.22.tgz#34d3ba35a965c437ed28279d31cd42d7698a4002" - integrity sha512-r2p7UkbFlFopAWUVprbECP+EpdjuEKPFQLhqpnHx9KxeTTLVaHuGpUNHye53MtYMoJFl9nJiMyIM7J2yY+dTQg== +v8-inspect-profiler@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/v8-inspect-profiler/-/v8-inspect-profiler-0.1.0.tgz#0d3f80e2dc878f737c31ae7ff4c033425a33a724" + integrity sha512-K7RyY4p59+rIPvgcTN/Oo7VU9cJ68LOl+dz8RCh/M4VwbZ9yS3Ci+qajbMDojW207anNn7CehkLvqpSIrNT9oA== dependencies: chrome-remote-interface "0.28.2" @@ -10426,7 +10426,7 @@ vscode-regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-ripgrep@^1.13.2: +vscode-ripgrep@^1.12.1, vscode-ripgrep@^1.13.2: version "1.13.2" resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.13.2.tgz#8ccebc33f14d54442c4b11962aead163c55b506e" integrity sha512-RlK9U87EokgHfiOjDQ38ipQQX936gWOcWPQaJpYf+kAkz1PQ1pK2n7nhiscdOmLu6XGjTs7pWFJ/ckonpN7twQ== From a224ef4fb3506d4c7307d3f5032f4af7d34d5a85 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Jan 2022 09:47:05 +0100 Subject: [PATCH 0967/2210] smoke - fix `selectTab` to select tab? --- test/automation/src/editors.ts | 41 +++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/test/automation/src/editors.ts b/test/automation/src/editors.ts index dac1f1e40dfb5..b3a914ffff026 100644 --- a/test/automation/src/editors.ts +++ b/test/automation/src/editors.ts @@ -18,22 +18,43 @@ export class Editors { } async selectTab(fileName: string): Promise { - await this.code.waitAndClick(`.tabs-container div.tab[data-resource-name$="${fileName}"]`); - await this.waitForEditorFocus(fileName); + + // Selecting a tab and making an editor have keyboard focus + // is critical to almost every test. As such, we try our + // best to retry this task in case some other component steals + // focus away from the editor while we attempt to get focus + + let error: unknown | undefined = undefined; + let retries = 0; + while (retries < 10) { + await this.code.waitAndClick(`.tabs-container div.tab[data-resource-name$="${fileName}"]`); + await this.code.dispatchKeybinding(process.platform === 'darwin' ? 'cmd+1' : 'ctrl+1'); // make editor really active if click failed somehow + + try { + await this.waitForEditorFocus(fileName, 50 /* 50 retries * 100ms delay = 5s */); + return; + } catch (e) { + error = e; + retries++; + } + } + + // We failed after 10 retries + throw error; } - async waitForActiveEditor(fileName: string): Promise { - const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] textarea`; - return this.code.waitForActiveElement(selector); + async waitForEditorFocus(fileName: string, retryCount?: number): Promise { + await this.waitForActiveTab(fileName, undefined, retryCount); + await this.waitForActiveEditor(fileName, retryCount); } - async waitForEditorFocus(fileName: string): Promise { - await this.waitForActiveTab(fileName); - await this.waitForActiveEditor(fileName); + async waitForActiveTab(fileName: string, isDirty: boolean = false, retryCount?: number): Promise { + await this.code.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][data-resource-name$="${fileName}"]`, undefined, retryCount); } - async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise { - await this.code.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][data-resource-name$="${fileName}"]`); + async waitForActiveEditor(fileName: string, retryCount?: number): Promise { + const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] textarea`; + return this.code.waitForActiveElement(selector, retryCount); } async waitForTab(fileName: string, isDirty: boolean = false): Promise { From 24b28ce145b747d82448d7466e7c557f6854f833 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Jan 2022 10:14:03 +0100 Subject: [PATCH 0968/2210] watcher - only stat root in some cases --- .../node/watcher/nodejs/nodejsWatcher.ts | 6 +++-- .../node/nodejsWatcher.integrationTest.ts | 24 ++----------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts index 45ae6bb505fb9..8dfa7fc710334 100644 --- a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts @@ -225,13 +225,15 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche // fs.watch() does not really help us figuring out // if the root folder got deleted. As such we have // to check if our watched path still exists and - // handle that accordingly. + // handle that accordingly. The only hint we get + // is that the event file name will be the same + // as the folder we are watching... // // We do not re-attach the watcher after timeout // though as we do for file watches because for // file watching specifically we want to handle // the atomic-write cases. - if (!await Promises.exists(path)) { + if (changedFileName === basename(path) && !await Promises.exists(path)) { this.onFileChange({ path: this.request.path, type: FileChangeType.DELETED }); } diff --git a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts index c5c8fbfdae7c8..aecad4dcb4dfa 100644 --- a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts @@ -11,7 +11,6 @@ import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/tes import { FileChangeType } from 'vs/platform/files/common/files'; import { IDiskFileChange } from 'vs/platform/files/common/watcher'; import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; -import { timeout } from 'vs/base/common/async'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; // this suite has shown flaky runs in Azure pipelines where @@ -71,7 +70,7 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; return Promises.rm(testDir).catch(error => console.error(error)); }); - async function awaitEvent(onDidChangeFile: Event, path: string, type: FileChangeType, failOnEventReason?: string): Promise { + async function awaitEvent(onDidChangeFile: Event, path: string, type: FileChangeType): Promise { if (loggingEnabled) { console.log(`Awaiting change type '${toMsg(type)}' on file '${path}'`); } @@ -82,11 +81,7 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; for (const event of events) { if (event.path === path && event.type === type) { disposable.dispose(); - if (failOnEventReason) { - reject(new Error(`Unexpected file event: ${failOnEventReason}`)); - } else { - setImmediate(() => resolve()); // copied from parcel watcher tests, seems to drop unrelated events on macOS - } + setImmediate(() => resolve()); // copied from parcel watcher tests, seems to drop unrelated events on macOS break; } } @@ -195,21 +190,6 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; await Promises.writeFile(anotherNewFilePath, 'Hello Another World'); await changeFuture; - // Read file does not emit event - changeFuture = awaitEvent(event, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-read-file'); - await Promises.readFile(anotherNewFilePath); - await Promise.race([timeout(100), changeFuture]); - - // Stat file does not emit event - changeFuture = awaitEvent(event, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); - await Promises.stat(anotherNewFilePath); - await Promise.race([timeout(100), changeFuture]); - - // Stat folder does not emit event - changeFuture = awaitEvent(event, copiedFolderpath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); - await Promises.stat(copiedFolderpath); - await Promise.race([timeout(100), changeFuture]); - // Delete file changeFuture = awaitEvent(event, copiedFilepath, FileChangeType.DELETED); await Promises.unlink(copiedFilepath); From 252abfe90afc6d15f8eb30353ae86c9bb31cc7c9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Jan 2022 11:21:53 +0100 Subject: [PATCH 0969/2210] :lipstick: main files --- src/vs/workbench/workbench.common.main.ts | 5 +- src/vs/workbench/workbench.desktop.main.ts | 55 ------------------- .../workbench.desktop.sandbox.main.ts | 12 ---- src/vs/workbench/workbench.sandbox.main.ts | 4 +- 4 files changed, 3 insertions(+), 73 deletions(-) diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 322fb90ab0c02..fa0f464f2a155 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -39,6 +39,7 @@ import 'vs/workbench/api/browser/viewsExtensionPoint'; //#region --- workbench parts + import 'vs/workbench/browser/parts/editor/editor.contribution'; import 'vs/workbench/browser/parts/editor/editorPart'; import 'vs/workbench/browser/parts/paneCompositePart'; @@ -63,6 +64,7 @@ import 'vs/workbench/services/preferences/browser/preferencesService'; import 'vs/workbench/services/configuration/common/jsonEditingService'; import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import 'vs/workbench/services/editor/browser/editorService'; +import 'vs/workbench/services/editor/browser/editorResolverService'; import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/services/keybinding/browser/keybindingService'; @@ -145,9 +147,6 @@ registerSingleton(IOpenerService, OpenerService, true); //#region --- workbench contributions -// Editor Override -import 'vs/workbench/services/editor/browser/editorResolverService'; - // Telemetry import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index ed5eada92aee5..75b3b27affafb 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -23,18 +23,6 @@ import 'vs/workbench/workbench.sandbox.main'; //#endregion -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// -// NOTE: Please do NOT register services here. Use `registerSingleton()` -// from `workbench.common.main.ts` if the service is shared between -// desktop and web or `workbench.sandbox.main.ts` if the service -// is desktop only. -// -// The `node` & `electron-browser` layer is deprecated for workbench! -// -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - //#region --- workbench services @@ -49,51 +37,8 @@ import 'vs/workbench/workbench.sandbox.main'; // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - import 'vs/workbench/services/extensions/electron-browser/extensionService'; - -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// -// NOTE: Please do NOT register services here. Use `registerSingleton()` -// from `workbench.common.main.ts` if the service is shared between -// desktop and web or `workbench.sandbox.main.ts` if the service -// is desktop only. -// -// The `node` & `electron-browser` layer is deprecated for workbench! -// -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - -//#endregion - - -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// -// NOTE: Please do NOT register services here. Use `registerSingleton()` -// from `workbench.common.main.ts` if the service is shared between -// desktop and web or `workbench.sandbox.main.ts` if the service -// is desktop only. -// -// The `node` & `electron-browser` layer is deprecated for workbench! -// -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - -//#region --- workbench contributions - -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// -// NOTE: Please do NOT register services here. Use `registerSingleton()` -// from `workbench.common.main.ts` if the service is shared between -// desktop and web or `workbench.sandbox.main.ts` if the service -// is desktop only. -// -// The `node` & `electron-browser` layer is deprecated for workbench! -// -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: Please do NOT register services here. Use `registerSingleton()` diff --git a/src/vs/workbench/workbench.desktop.sandbox.main.ts b/src/vs/workbench/workbench.desktop.sandbox.main.ts index 8ae202e8720dd..792585471994c 100644 --- a/src/vs/workbench/workbench.desktop.sandbox.main.ts +++ b/src/vs/workbench/workbench.desktop.sandbox.main.ts @@ -18,12 +18,6 @@ import 'vs/workbench/workbench.sandbox.main'; //#endregion -//#region --- workbench actions - - -//#endregion - - //#region --- workbench (desktop main) import 'vs/workbench/electron-sandbox/desktop.main'; @@ -41,10 +35,4 @@ class SimpleExtensionService extends NullExtensionService { } registerSingleton(IExtensionService, SimpleExtensionService); - -//#endregion - - -//#region --- workbench contributions - //#endregion diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index 03ff1f7147c52..7c3a50d21da91 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -20,6 +20,7 @@ import 'vs/workbench/workbench.common.main'; //#region --- workbench (desktop main) import 'vs/workbench/electron-sandbox/desktop.main'; +import 'vs/workbench/electron-sandbox/desktop.contribution'; //#endregion @@ -99,9 +100,6 @@ import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution'; // Localizations import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; -// Desktop -import 'vs/workbench/electron-sandbox/desktop.contribution'; - // Explorer import 'vs/workbench/contrib/files/electron-sandbox/files.contribution'; import 'vs/workbench/contrib/files/electron-sandbox/fileActions.contribution'; From 76f34266d50c5b0495145684aef9187469cee761 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 4 Jan 2022 11:34:42 +0100 Subject: [PATCH 0970/2210] Replace RegEx with trim() --- extensions/git/src/git.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 19f4a4bcb808e..eb546c026a0a2 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -84,7 +84,7 @@ function findGitDarwin(onValidate: (path: string) => boolean): Promise { return e('git not found'); } - const path = gitPathBuffer.toString().replace(/^\s+|\s+$/g, ''); + const path = gitPathBuffer.toString().trim(); function getVersion(path: string) { if (!onValidate(path)) { From 25553c854db21b3ca9a0cabf1ba88e86076c7919 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 4 Jan 2022 12:06:14 +0100 Subject: [PATCH 0971/2210] Remove unnecessary import --- src/vs/workbench/workbench.common.main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index fa0f464f2a155..ee1856d0b2081 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -52,7 +52,6 @@ import 'vs/workbench/browser/parts/views/viewsService'; //#region --- workbench services -import 'vs/platform/workspace/common/workspaceTrust'; import 'vs/platform/undoRedo/common/undoRedoService'; import 'vs/workbench/services/extensions/browser/extensionUrlHandler'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; From 4013a05976f4918ea5ca9372c0847a3854c4b076 Mon Sep 17 00:00:00 2001 From: Nafiur Rahman Khadem Date: Tue, 4 Jan 2022 20:43:50 +0600 Subject: [PATCH 0972/2210] Add an undo/redo stop at end of snippet --- src/vs/editor/contrib/snippet/snippetController2.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index 5e17ff58f1083..7f51e2650ac4d 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -153,6 +153,7 @@ export class SnippetController2 implements IEditorContribution { } if (this._session.isAtLastPlaceholder || !this._session.isSelectionWithinPlaceholders()) { + this._editor.getModel().pushStackElement(); return this.cancel(); } From f8d5df4621094b80954057a9ee7f397a4c26bc4e Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Tue, 4 Jan 2022 16:11:51 +0100 Subject: [PATCH 0973/2210] localize strings; fixes #139836 --- src/vs/workbench/contrib/debug/browser/disassemblyView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index 4681e8b3da357..f7a7aeb6057c1 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -168,7 +168,7 @@ export class DisassemblyView extends EditorPane { project(row: IDisassembledInstructionEntry): IDisassembledInstructionEntry { return row; } }, { - label: 'instructions', + label: localize('disassemblyTableColumnLabel', "instructions"), tooltip: '', weight: 0.3, templateId: InstructionRenderer.TEMPLATE_ID, @@ -714,7 +714,7 @@ class InstructionRenderer extends Disposable implements ITableRenderer Date: Tue, 4 Jan 2022 16:29:24 +0100 Subject: [PATCH 0974/2210] assume LinkedText from inlay hint object and create a decoration for each part/node, prep for https://github.com/microsoft/vscode/issues/129528 --- src/vs/base/common/linkedText.ts | 2 +- .../inlayHints/inlayHintsController.ts | 144 +++++++++++------- 2 files changed, 91 insertions(+), 55 deletions(-) diff --git a/src/vs/base/common/linkedText.ts b/src/vs/base/common/linkedText.ts index 5f6c6a05dcf55..c9a6dd3e658de 100644 --- a/src/vs/base/common/linkedText.ts +++ b/src/vs/base/common/linkedText.ts @@ -23,7 +23,7 @@ export class LinkedText { } } -const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: ("|')([^\3]+)(\3))?\)/gi; +const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:|file:)[^\)\s]+)(?: ("|')([^\3]+)(\3))?\)/gi; export function parseLinkedText(text: string): LinkedText { const result: LinkedTextNode[] = []; diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 0180fee510352..a07a9d2c427ef 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -3,29 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { parseLinkedText } from 'vs/base/common/linkedText'; import { LRUCache, ResourceMap } from 'vs/base/common/map'; import { IRange } from 'vs/base/common/range'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { DynamicCssRules } from 'vs/editor/browser/editorDom'; +import { CssProperties, DynamicCssRules } from 'vs/editor/browser/editorDom'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, IWordAtPosition, TrackedRangeStickiness } from 'vs/editor/common/model'; import { InlayHint, InlayHintKind, InlayHintsProvider, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; import { LanguageFeatureRequestDelays } from 'vs/editor/common/languages/languageFeatureRegistry'; +import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, IWordAtPosition, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { editorInlayHintBackground, editorInlayHintForeground, editorInlayHintParameterBackground, editorInlayHintParameterForeground, editorInlayHintTypeBackground, editorInlayHintTypeForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ThemeColor, themeColorFromId } from 'vs/platform/theme/common/themeService'; - +import * as colors from 'vs/platform/theme/common/colorRegistry'; +import { themeColorFromId } from 'vs/platform/theme/common/themeService'; const MAX_DECORATORS = 1500; @@ -110,7 +111,7 @@ export class InlayHintsController implements IEditorContribution { private readonly _sessionDisposables = new DisposableStore(); private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 25, 500); private readonly _cache = new InlayHintsCache(); - private readonly _decorationsMetadata = new Map(); + private readonly _decorationsMetadata = new Map(); private readonly _ruleFactory = new DynamicCssRules(this._editor); constructor( @@ -167,7 +168,7 @@ export class InlayHintsController implements IEditorContribution { return; } this._updateHintsDecorators(ranges, result); - this._cache.set(model, Array.from(this._decorationsMetadata.values()).map(obj => obj.hint)); + this._cache.set(model, distinct(Array.from(this._decorationsMetadata.values(), obj => obj.hint))); }, this._getInlayHintsDelays.get(model)); @@ -213,43 +214,17 @@ export class InlayHintsController implements IEditorContribution { const { fontSize, fontFamily } = this._getLayoutInfo(); const model = this._editor.getModel()!; - const newDecorationsData: { decoration: IModelDeltaDecoration, classNameRef: IDisposable }[] = []; + const newDecorationsData: { hint: InlayHint, decoration: IModelDeltaDecoration, linkTarget?: string, classNameRef: IDisposable }[] = []; const fontFamilyVar = '--code-editorInlayHintsFontFamily'; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); for (const hint of hints) { - const { text, position, whitespaceBefore, whitespaceAfter } = hint; - const marginBefore = whitespaceBefore ? (fontSize / 3) | 0 : 0; - const marginAfter = whitespaceAfter ? (fontSize / 3) | 0 : 0; - - let backgroundColor: ThemeColor; - let color: ThemeColor; - if (hint.kind === InlayHintKind.Parameter) { - backgroundColor = themeColorFromId(editorInlayHintParameterBackground); - color = themeColorFromId(editorInlayHintParameterForeground); - } else if (hint.kind === InlayHintKind.Type) { - backgroundColor = themeColorFromId(editorInlayHintTypeBackground); - color = themeColorFromId(editorInlayHintTypeForeground); - } else { - backgroundColor = themeColorFromId(editorInlayHintBackground); - color = themeColorFromId(editorInlayHintForeground); - } - - const classNameRef = this._ruleFactory.createClassNameRef({ - fontSize: `${fontSize}px`, - margin: `0px ${marginAfter}px 0px ${marginBefore}px`, - fontFamily: `var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}`, - padding: `1px ${Math.max(1, fontSize / 4) | 0}px`, - borderRadius: `${(fontSize / 4) | 0}px`, - verticalAlign: 'middle', - backgroundColor, - color - }); + const { position, whitespaceBefore, whitespaceAfter } = hint; + // position let direction: 'before' | 'after' = 'before'; - let range = Range.fromPositions(position); let word = model.getWordAtPosition(position); let usesWordRange = false; @@ -264,22 +239,70 @@ export class InlayHintsController implements IEditorContribution { } } - newDecorationsData.push({ - decoration: { - range, - options: { - [direction]: { - content: fixSpace(text), - inlineClassNameAffectsLetterSpacing: true, - inlineClassName: classNameRef.className, - } as InjectedTextOptions, - description: 'InlayHint', - showIfCollapsed: !usesWordRange, - stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges - } - }, - classNameRef - }); + // text w/ links + const { nodes } = parseLinkedText(hint.text); + const marginBefore = whitespaceBefore ? (fontSize / 3) | 0 : 0; + const marginAfter = whitespaceAfter ? (fontSize / 3) | 0 : 0; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + + const isFirst = i === 0; + const isLast = i === nodes.length - 1; + const isLink = typeof node === 'object'; + + const cssProperties: CssProperties = { + fontSize: `${fontSize}px`, + fontFamily: `var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}`, + verticalAlign: 'middle', + }; + + this._fillInColors(cssProperties, hint); + + if (isLink) { + cssProperties.textDecoration = 'underline'; + } + + if (isFirst && isLast) { + // only element + cssProperties.margin = `0px ${marginAfter}px 0px ${marginBefore}px`; + cssProperties.padding = `1px ${Math.max(1, fontSize / 4) | 0}px`; + cssProperties.borderRadius = `${(fontSize / 4) | 0}px`; + } else if (isFirst) { + // first element + cssProperties.margin = `0px 0 0 ${marginAfter}px`; + cssProperties.padding = `1px 0 0 ${Math.max(1, fontSize / 4) | 0}px`; + cssProperties.borderRadius = `${(fontSize / 4) | 0}px 0 0 ${(fontSize / 4) | 0}px`; + } else if (isLast) { + // last element + cssProperties.margin = `0px ${marginAfter}px 0 0`; + cssProperties.padding = `1px ${Math.max(1, fontSize / 4) | 0}px 0 0`; + cssProperties.borderRadius = `0 ${(fontSize / 4) | 0}px ${(fontSize / 4) | 0}px 0`; + } else { + cssProperties.padding = `1px 0 1px 0`; + } + + const classNameRef = this._ruleFactory.createClassNameRef(cssProperties); + + newDecorationsData.push({ + hint, + linkTarget: isLink ? node.href : undefined, + classNameRef, + decoration: { + range, + options: { + [direction]: { + content: fixSpace(isLink ? node.label : node), + inlineClassNameAffectsLetterSpacing: true, + inlineClassName: classNameRef.className, + } as InjectedTextOptions, + description: 'InlayHint', + showIfCollapsed: !usesWordRange, + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges + } + }, + }); + } if (newDecorationsData.length > MAX_DECORATORS) { break; @@ -301,7 +324,21 @@ export class InlayHintsController implements IEditorContribution { } const newDecorationIds = model.deltaDecorations(decorationIdsToReplace, newDecorationsData.map(d => d.decoration), this._decorationOwnerId); for (let i = 0; i < newDecorationIds.length; i++) { - this._decorationsMetadata.set(newDecorationIds[i], { hint: hints[i], classNameRef: newDecorationsData[i].classNameRef }); + const data = newDecorationsData[i]; + this._decorationsMetadata.set(newDecorationIds[i], { hint: data.hint, classNameRef: data.classNameRef, linkTarget: data.linkTarget }); + } + } + + private _fillInColors(props: CssProperties, hint: InlayHint): void { + if (hint.kind === InlayHintKind.Parameter) { + props.backgroundColor = themeColorFromId(colors.editorInlayHintParameterBackground); + props.color = themeColorFromId(colors.editorInlayHintParameterForeground); + } else if (hint.kind === InlayHintKind.Type) { + props.backgroundColor = themeColorFromId(colors.editorInlayHintTypeBackground); + props.color = themeColorFromId(colors.editorInlayHintTypeForeground); + } else { + props.backgroundColor = themeColorFromId(colors.editorInlayHintBackground); + props.color = themeColorFromId(colors.editorInlayHintForeground); } } @@ -357,4 +394,3 @@ CommandsRegistry.registerCommand('_executeInlayHintProvider', async (accessor, . ref.dispose(); } }); - From 18ab7aca37765e2140a320b93e5efd8189bea22a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 4 Jan 2022 16:34:18 +0100 Subject: [PATCH 0975/2210] Include injected text details in mouse click/hover event. --- src/vs/editor/browser/controller/mouseTarget.ts | 13 +++++++------ src/vs/editor/common/model.ts | 6 ++++++ src/vs/editor/common/model/textModel.ts | 2 ++ src/vs/monaco.d.ts | 5 +++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 1212581747f95..5aadb8190aec7 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -44,6 +44,7 @@ export interface IEmptyContentData { export interface ITextContentData { mightBeForeignElement: boolean; + injectedText: InjectedText | null; } const enum HitTestResultType { @@ -569,7 +570,7 @@ export class MouseTargetFactory { for (const d of lastViewCursorsRenderData) { if (request.target === d.domNode) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position, null, { mightBeForeignElement: false }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position, null, { mightBeForeignElement: false, injectedText: null }); } } } @@ -601,7 +602,7 @@ export class MouseTargetFactory { cursorVerticalOffset <= mouseVerticalOffset && mouseVerticalOffset <= cursorVerticalOffset + d.height ) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position, null, { mightBeForeignElement: false }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position, null, { mightBeForeignElement: false, injectedText: null }); } } } @@ -623,7 +624,7 @@ export class MouseTargetFactory { // Is it the textarea? if (ElementPath.isTextArea(request.targetPath)) { if (ctx.lastRenderData.lastTextareaPosition) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, ctx.lastRenderData.lastTextareaPosition, null, { mightBeForeignElement: false }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, ctx.lastRenderData.lastTextareaPosition, null, { mightBeForeignElement: false, injectedText: null }); } return request.fulfill(MouseTargetType.TEXTAREA, ctx.lastRenderData.lastTextareaPosition); } @@ -782,7 +783,7 @@ export class MouseTargetFactory { const columnHorizontalOffset = visibleRange.left; if (request.mouseContentHorizontalOffset === columnHorizontalOffset) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !!injectedText }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !!injectedText, injectedText }); } // Let's define a, b, c and check if the offset is in between them... @@ -815,10 +816,10 @@ export class MouseTargetFactory { const curr = points[i]; if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) { const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column); - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); } } - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText }); + return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); } /** diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 87d578e1fb938..1e7f9803fee48 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -202,6 +202,12 @@ export interface InjectedTextOptions { * If there is an `inlineClassName` which affects letter spacing. */ readonly inlineClassNameAffectsLetterSpacing?: boolean; + + /** + * This field allows to attach data to this injected text. + * The data can be read when injected texts at a given position are queried. + */ + readonly attachedData?: unknown; } /** diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 72ecfb6eb6aa9..21061b82d8d5b 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2497,11 +2497,13 @@ export class ModelDecorationInjectedTextOptions implements model.InjectedTextOpt public readonly content: string; readonly inlineClassName: string | null; readonly inlineClassNameAffectsLetterSpacing: boolean; + readonly attachedData: unknown | null; private constructor(options: model.InjectedTextOptions) { this.content = options.content || ''; this.inlineClassName = options.inlineClassName || null; this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false; + this.attachedData = options.attachedData || null; } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6c022f105002b..443ee9b3450fc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1501,6 +1501,11 @@ declare namespace monaco.editor { * If there is an `inlineClassName` which affects letter spacing. */ readonly inlineClassNameAffectsLetterSpacing?: boolean; + /** + * This field allows to attach data to this injected text. + * The data can be read when injected texts at a given position are queried. + */ + readonly attachedData?: unknown; } /** From 1b6e853df16c5289082c63bf391f5ec62479f2ac Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Jan 2022 17:44:22 +0100 Subject: [PATCH 0976/2210] support to click on linked inlay hints, https://github.com/microsoft/vscode/issues/129528 --- .../inlayHints/inlayHintsController.ts | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index a07a9d2c427ef..eb2a6ddfbe112 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -13,7 +13,7 @@ import { LRUCache, ResourceMap } from 'vs/base/common/map'; import { IRange } from 'vs/base/common/range'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { CssProperties, DynamicCssRules } from 'vs/editor/browser/editorDom'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; @@ -23,8 +23,10 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { InlayHint, InlayHintKind, InlayHintsProvider, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; import { LanguageFeatureRequestDelays } from 'vs/editor/common/languages/languageFeatureRegistry'; import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, IWordAtPosition, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -100,6 +102,10 @@ class InlayHintsCache { } } +class InlayHintLink { + constructor(readonly href: string) { } +} + export class InlayHintsController implements IEditorContribution { static readonly ID: string = 'editor.contrib.InlayHints'; @@ -111,11 +117,12 @@ export class InlayHintsController implements IEditorContribution { private readonly _sessionDisposables = new DisposableStore(); private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 25, 500); private readonly _cache = new InlayHintsCache(); - private readonly _decorationsMetadata = new Map(); + private readonly _decorationsMetadata = new Map(); private readonly _ruleFactory = new DynamicCssRules(this._editor); constructor( - private readonly _editor: ICodeEditor + private readonly _editor: ICodeEditor, + @IOpenerService private readonly _openerService: IOpenerService, ) { this._disposables.add(InlayHintsProviderRegistry.onDidChange(() => this._update())); this._disposables.add(_editor.onDidChangeModel(() => this._update())); @@ -191,6 +198,17 @@ export class InlayHintsController implements IEditorContribution { })); } } + + // link click listener + this._sessionDisposables.add(this._editor.onMouseUp(e => { + if (e.target.type !== MouseTargetType.CONTENT_TEXT || typeof e.target.detail !== 'object') { + return; + } + const options = e.target.detail.injectedText?.options; + if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLink) { + this._openerService.open(options.attachedData.href, { allowCommands: true }); + } + })); } private _getHintsRanges(): Range[] { @@ -214,7 +232,7 @@ export class InlayHintsController implements IEditorContribution { const { fontSize, fontFamily } = this._getLayoutInfo(); const model = this._editor.getModel()!; - const newDecorationsData: { hint: InlayHint, decoration: IModelDeltaDecoration, linkTarget?: string, classNameRef: IDisposable }[] = []; + const newDecorationsData: { hint: InlayHint, decoration: IModelDeltaDecoration, classNameRef: IDisposable }[] = []; const fontFamilyVar = '--code-editorInlayHintsFontFamily'; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); @@ -261,6 +279,7 @@ export class InlayHintsController implements IEditorContribution { if (isLink) { cssProperties.textDecoration = 'underline'; + // cssProperties.cursor = 'pointer'; } if (isFirst && isLast) { @@ -286,7 +305,6 @@ export class InlayHintsController implements IEditorContribution { newDecorationsData.push({ hint, - linkTarget: isLink ? node.href : undefined, classNameRef, decoration: { range, @@ -295,6 +313,7 @@ export class InlayHintsController implements IEditorContribution { content: fixSpace(isLink ? node.label : node), inlineClassNameAffectsLetterSpacing: true, inlineClassName: classNameRef.className, + attachedData: isLink ? new InlayHintLink(node.href) : undefined } as InjectedTextOptions, description: 'InlayHint', showIfCollapsed: !usesWordRange, @@ -325,7 +344,7 @@ export class InlayHintsController implements IEditorContribution { const newDecorationIds = model.deltaDecorations(decorationIdsToReplace, newDecorationsData.map(d => d.decoration), this._decorationOwnerId); for (let i = 0; i < newDecorationIds.length; i++) { const data = newDecorationsData[i]; - this._decorationsMetadata.set(newDecorationIds[i], { hint: data.hint, classNameRef: data.classNameRef, linkTarget: data.linkTarget }); + this._decorationsMetadata.set(newDecorationIds[i], { hint: data.hint, classNameRef: data.classNameRef }); } } From 79ed98edc92d62aca259c26f2bb74092abf270f8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Jan 2022 09:40:49 -0800 Subject: [PATCH 0977/2210] Skip flaky tests Part of #140110 --- .../vscode-api-tests/src/singlefolder-tests/terminal.test.ts | 4 ++-- .../src/singlefolder-tests/workspace.tasks.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 0fa697a610506..5f5463b846bae 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -712,7 +712,7 @@ import { assertNoRpc, poll } from '../utils'; }); }); - test('should have collection variables apply to environment variables that don\'t exist', async () => { + test.skip('should have collection variables apply to environment variables that don\'t exist', async () => { // Setup collection and create terminal const collection = extensionContext.environmentVariableCollection; disposables.push({ dispose: () => collection.clear() }); @@ -798,7 +798,7 @@ import { assertNoRpc, poll } from '../utils'; }); }); - test('should respect deleting entries', async () => { + test.skip('should respect deleting entries', async () => { // Setup collection and create terminal const collection = extensionContext.environmentVariableCollection; disposables.push({ dispose: () => collection.clear() }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index 9f8da6ceb94d0..b1274fdbb8753 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -76,7 +76,7 @@ import { assertNoRpc } from '../utils'; }); }); - test('dependsOn task should start with a different processId (#118256)', async () => { + test.skip('dependsOn task should start with a different processId (#118256)', async () => { // Set up dependsOn task by creating tasks.json since this is not possible via the API // Tasks API const tasksConfig = workspace.getConfiguration('tasks'); From b61f32323db74cf9e4945431a835ba15190f64e0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Jan 2022 18:42:39 +0100 Subject: [PATCH 0978/2210] use ClickLinkGesture for inlay hint links, https://github.com/microsoft/vscode/issues/129528 --- .../inlayHints/inlayHintsController.ts | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index eb2a6ddfbe112..13d76d9c8ccf5 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -25,6 +25,7 @@ import { LanguageFeatureRequestDelays } from 'vs/editor/common/languages/languag import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, IWordAtPosition, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ClickLinkGesture } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import * as colors from 'vs/platform/theme/common/colorRegistry'; @@ -199,16 +200,40 @@ export class InlayHintsController implements IEditorContribution { } } - // link click listener - this._sessionDisposables.add(this._editor.onMouseUp(e => { - if (e.target.type !== MouseTargetType.CONTENT_TEXT || typeof e.target.detail !== 'object') { + // link gesture + let undoHover = () => { }; + const gesture = this._sessionDisposables.add(new ClickLinkGesture(this._editor)); + this._sessionDisposables.add(gesture.onMouseMoveOrRelevantKeyDown(e => { + const [mouseEvent] = e; + if (mouseEvent.target.type !== MouseTargetType.CONTENT_TEXT || typeof mouseEvent.target.detail !== 'object' || !mouseEvent.hasTriggerModifier) { + undoHover(); return; } - const options = e.target.detail.injectedText?.options; + const options = mouseEvent.target.detail?.injectedText?.options; + if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLink) { + if (mouseEvent.target.element instanceof HTMLElement) { + // todo@jrieken not proper, won't work with wrapped lines. use decoration instead + mouseEvent.target.element.style.cursor = 'pointer'; + mouseEvent.target.element.style.color = `var(${colors.asCssVariableName(colors.editorActiveLinkForeground)})`; + undoHover = () => { + (mouseEvent.target.element).style.cursor = ''; + (mouseEvent.target.element).style.color = ''; + undoHover = () => { }; + }; + } + } + })); + this._sessionDisposables.add(gesture.onCancel(undoHover)); + this._sessionDisposables.add(gesture.onExecute(e => { + if (e.target.type !== MouseTargetType.CONTENT_TEXT || typeof e.target.detail !== 'object' || !e.hasTriggerModifier) { + return; + } + const options = e.target.detail?.injectedText?.options; if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLink) { this._openerService.open(options.attachedData.href, { allowCommands: true }); } })); + } private _getHintsRanges(): Range[] { From 0a546ffff25a01902c798ab87a6643b70faacebe Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 4 Jan 2022 10:03:46 -0800 Subject: [PATCH 0979/2210] Use dash separated css class #137549 --- src/vs/base/browser/ui/sash/sash.css | 2 +- src/vs/base/browser/ui/sash/sash.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index 687172eed5bb4..11cd5172cdcab 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -121,7 +121,7 @@ top: calc(50% - (var(--sash-hover-size) / 2)); } -.pointerEventsDisabled { +.pointer-events-disabled { pointer-events: none !important; } diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 94380394d5bb1..c39e05486eadf 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -222,7 +222,7 @@ class OrthogonalPointerEventFactory implements IPointerEventFactory { } } -const PointerEventsDisabledCssClass = 'pointerEventsDisabled'; +const PointerEventsDisabledCssClass = 'pointer-events-disabled'; /** * The {@link Sash} is the UI component which allows the user to resize other From 4412993598527c8373001924c12d160a1b68fc6d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 4 Jan 2022 10:39:49 -0800 Subject: [PATCH 0980/2210] fixes #139903 --- .../browser/parts/activitybar/activitybarPart.ts | 5 +++++ src/vs/workbench/browser/parts/paneCompositePart.ts | 9 +++++++++ src/vs/workbench/browser/parts/panel/panelPart.ts | 5 +++++ .../contrib/quickaccess/browser/viewQuickAccess.ts | 4 ++-- .../services/panecomposite/browser/panecomposite.ts | 5 +++++ src/vs/workbench/test/browser/workbenchTestServices.ts | 5 +++++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b2b74a071eca7..1a1daf8fc7b77 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -750,6 +750,11 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart .map(v => v.id); } + getAllPaneCompositeIds(): string[] { + return this.compositeBar.getVisibleComposites() + .map(v => v.id); + } + focus(): void { this.compositeBar.focus(); } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index d80ef752be3ac..d2427bffc7d3d 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -72,6 +72,11 @@ export interface IPaneCompositeSelectorPart { */ getVisiblePaneCompositeIds(): string[]; + /** + * Returns id of all view containers following the visual order including those not pinned or visible. + */ + getAllPaneCompositeIds(): string[]; + /** * Show an activity in a viewlet. */ @@ -126,6 +131,10 @@ export class PaneCompositeParts implements IPaneCompositePartService { return this.getSelectorPartByLocation(viewContainerLocation).getVisiblePaneCompositeIds(); } + getOrderedPaneCompositeIds(viewContainerLocation: ViewContainerLocation): string[] { + return this.getSelectorPartByLocation(viewContainerLocation).getAllPaneCompositeIds(); + } + getProgressIndicator(id: string, viewContainerLocation: ViewContainerLocation): IProgressIndicator | undefined { return this.getPartByLocation(viewContainerLocation).getProgressIndicator(id); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 266460d0b0262..0fb67356086b4 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -591,6 +591,11 @@ export abstract class BasePanelPart extends CompositePart impleme .map(v => v.id); } + getAllPaneCompositeIds(): string[] { + return this.compositeBar.getVisibleComposites() + .map(v => v.id); + } + getActivePaneComposite(): IPaneComposite | undefined { return this.getActiveComposite(); } diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 7e31652274b0c..3d70573d9d1bd 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -114,7 +114,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider { - const paneComposites = this.paneCompositeService.getPaneComposites(location); + const paneComposites = this.paneCompositeService.getOrderedPaneCompositeIds(location).map(id => { return { id }; }); for (const paneComposite of paneComposites) { if (this.includeViewContainer(paneComposite)) { const viewContainer = this.viewDescriptorService.getViewContainerById(paneComposite.id); @@ -176,7 +176,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider 0; diff --git a/src/vs/workbench/services/panecomposite/browser/panecomposite.ts b/src/vs/workbench/services/panecomposite/browser/panecomposite.ts index e25a1264be447..158341a97cc28 100644 --- a/src/vs/workbench/services/panecomposite/browser/panecomposite.ts +++ b/src/vs/workbench/services/panecomposite/browser/panecomposite.ts @@ -51,6 +51,11 @@ export interface IPaneCompositePartService { */ getVisiblePaneCompositeIds(viewContainerLocation: ViewContainerLocation): string[]; + /** + * Returns id of all view containers following the visual order. + */ + getOrderedPaneCompositeIds(viewContainerLocation: ViewContainerLocation): string[]; + /** * Returns the progress indicator for the side bar. */ diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 5e9c9e87bee44..795c0fc07fc94 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -640,6 +640,10 @@ export class TestPaneCompositeService extends Disposable implements IPaneComposi throw new Error('Method not implemented.'); } + getOrderedPaneCompositeIds(viewContainerLocation: ViewContainerLocation): string[] { + throw new Error('Method not implemented.'); + } + showActivity(id: string, viewContainerLocation: ViewContainerLocation, badge: IBadge, clazz?: string, priority?: number): IDisposable { throw new Error('Method not implemented.'); } @@ -696,6 +700,7 @@ export class TestPanelPart implements IPaneCompositePart, IPaneCompositeSelector getPaneComposites() { return []; } getPinnedPaneCompositeIds() { return []; } getVisiblePaneCompositeIds() { return []; } + getAllPaneCompositeIds() { return []; } getActivePaneComposite(): IPaneComposite { return activeViewlet; } setPanelEnablement(id: string, enabled: boolean): void { } dispose() { } From 63aa67ac16ccd98c2037f527a32ded29597f529d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 4 Jan 2022 10:48:05 -0800 Subject: [PATCH 0981/2210] Revert "fixes #139903" This reverts commit 4412993598527c8373001924c12d160a1b68fc6d. --- .../browser/parts/activitybar/activitybarPart.ts | 5 ----- src/vs/workbench/browser/parts/paneCompositePart.ts | 9 --------- src/vs/workbench/browser/parts/panel/panelPart.ts | 5 ----- .../contrib/quickaccess/browser/viewQuickAccess.ts | 4 ++-- .../services/panecomposite/browser/panecomposite.ts | 5 ----- src/vs/workbench/test/browser/workbenchTestServices.ts | 5 ----- 6 files changed, 2 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 1a1daf8fc7b77..b2b74a071eca7 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -750,11 +750,6 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart .map(v => v.id); } - getAllPaneCompositeIds(): string[] { - return this.compositeBar.getVisibleComposites() - .map(v => v.id); - } - focus(): void { this.compositeBar.focus(); } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index d2427bffc7d3d..d80ef752be3ac 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -72,11 +72,6 @@ export interface IPaneCompositeSelectorPart { */ getVisiblePaneCompositeIds(): string[]; - /** - * Returns id of all view containers following the visual order including those not pinned or visible. - */ - getAllPaneCompositeIds(): string[]; - /** * Show an activity in a viewlet. */ @@ -131,10 +126,6 @@ export class PaneCompositeParts implements IPaneCompositePartService { return this.getSelectorPartByLocation(viewContainerLocation).getVisiblePaneCompositeIds(); } - getOrderedPaneCompositeIds(viewContainerLocation: ViewContainerLocation): string[] { - return this.getSelectorPartByLocation(viewContainerLocation).getAllPaneCompositeIds(); - } - getProgressIndicator(id: string, viewContainerLocation: ViewContainerLocation): IProgressIndicator | undefined { return this.getPartByLocation(viewContainerLocation).getProgressIndicator(id); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 0fb67356086b4..266460d0b0262 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -591,11 +591,6 @@ export abstract class BasePanelPart extends CompositePart impleme .map(v => v.id); } - getAllPaneCompositeIds(): string[] { - return this.compositeBar.getVisibleComposites() - .map(v => v.id); - } - getActivePaneComposite(): IPaneComposite | undefined { return this.getActiveComposite(); } diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 3d70573d9d1bd..7e31652274b0c 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -114,7 +114,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider { - const paneComposites = this.paneCompositeService.getOrderedPaneCompositeIds(location).map(id => { return { id }; }); + const paneComposites = this.paneCompositeService.getPaneComposites(location); for (const paneComposite of paneComposites) { if (this.includeViewContainer(paneComposite)) { const viewContainer = this.viewDescriptorService.getViewContainerById(paneComposite.id); @@ -176,7 +176,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider 0; diff --git a/src/vs/workbench/services/panecomposite/browser/panecomposite.ts b/src/vs/workbench/services/panecomposite/browser/panecomposite.ts index 158341a97cc28..e25a1264be447 100644 --- a/src/vs/workbench/services/panecomposite/browser/panecomposite.ts +++ b/src/vs/workbench/services/panecomposite/browser/panecomposite.ts @@ -51,11 +51,6 @@ export interface IPaneCompositePartService { */ getVisiblePaneCompositeIds(viewContainerLocation: ViewContainerLocation): string[]; - /** - * Returns id of all view containers following the visual order. - */ - getOrderedPaneCompositeIds(viewContainerLocation: ViewContainerLocation): string[]; - /** * Returns the progress indicator for the side bar. */ diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 795c0fc07fc94..5e9c9e87bee44 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -640,10 +640,6 @@ export class TestPaneCompositeService extends Disposable implements IPaneComposi throw new Error('Method not implemented.'); } - getOrderedPaneCompositeIds(viewContainerLocation: ViewContainerLocation): string[] { - throw new Error('Method not implemented.'); - } - showActivity(id: string, viewContainerLocation: ViewContainerLocation, badge: IBadge, clazz?: string, priority?: number): IDisposable { throw new Error('Method not implemented.'); } @@ -700,7 +696,6 @@ export class TestPanelPart implements IPaneCompositePart, IPaneCompositeSelector getPaneComposites() { return []; } getPinnedPaneCompositeIds() { return []; } getVisiblePaneCompositeIds() { return []; } - getAllPaneCompositeIds() { return []; } getActivePaneComposite(): IPaneComposite { return activeViewlet; } setPanelEnablement(id: string, enabled: boolean): void { } dispose() { } From cc69e10017c51779124ef05616b3ff4e6ee9f35d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 4 Jan 2022 10:55:05 -0800 Subject: [PATCH 0982/2210] fixes #139903 --- .../quickaccess/browser/viewQuickAccess.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 7e31652274b0c..609a20ffce346 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -115,6 +115,23 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider { const paneComposites = this.paneCompositeService.getPaneComposites(location); + const visiblePaneCompositeIds = this.paneCompositeService.getVisiblePaneCompositeIds(location); + + paneComposites.sort((a, b) => { + let aIndex = visiblePaneCompositeIds.findIndex(id => a.id === id); + let bIndex = visiblePaneCompositeIds.findIndex(id => b.id === id); + + if (aIndex < 0) { + aIndex = paneComposites.indexOf(a) + visiblePaneCompositeIds.length; + } + + if (bIndex < 0) { + bIndex = paneComposites.indexOf(b) + visiblePaneCompositeIds.length; + } + + return aIndex - bIndex; + }); + for (const paneComposite of paneComposites) { if (this.includeViewContainer(paneComposite)) { const viewContainer = this.viewDescriptorService.getViewContainerById(paneComposite.id); From f0e8c022f961a59b2fbbb391e61d09c2f3a1ab64 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 4 Jan 2022 11:16:29 -0800 Subject: [PATCH 0983/2210] Move didPause from cell metadata to execution service --- .../contrib/breakpoints/notebookBreakpoints.ts | 10 ++++++++-- .../notebookExecutionStateServiceImpl.ts | 17 ++++++++++++----- .../contrib/notebook/common/notebookCommon.ts | 1 - .../common/notebookExecutionStateService.ts | 2 ++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts b/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts index 570d2dd93f85a..568dca1aad126 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts @@ -16,6 +16,8 @@ import { Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellEditType, CellUri, NotebookCellsChangeType, NullablePartialNotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -133,7 +135,8 @@ class NotebookCellPausing extends Disposable implements IWorkbenchContribution { constructor( @IDebugService private readonly _debugService: IDebugService, - @INotebookService private readonly _notebookService: INotebookService + @INotebookService private readonly _notebookService: INotebookService, + @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, ) { super(); @@ -178,7 +181,10 @@ class NotebookCellPausing extends Disposable implements IWorkbenchContribution { isPaused }; if (isPaused) { - internalMetadata.didPause = true; + this._notebookExecutionStateService.updateNotebookCellExecution(parsed.notebook, parsed.handle, [{ + editType: CellExecutionUpdateType.ExecutionState, + didPause: true + }]); } if (notebookModel?.checkCellExistence(parsed.handle)) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts index 884f2ed5072f5..85caa5828507b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts @@ -292,6 +292,11 @@ class CellExecution implements ICellExecutionEntry { return this._notebookModel.uri; } + private _didPause = false; + get didPause() { + return this._didPause; + } + constructor( readonly cellHandle: number, private readonly _notebookModel: NotebookTextModel, @@ -305,7 +310,6 @@ class CellExecution implements ICellExecutionEntry { runEndTime: null, lastRunSuccess: null, executionOrder: null, - didPause: false } }; this._applyExecutionEdits([startExecuteEdit]); @@ -316,6 +320,10 @@ class CellExecution implements ICellExecutionEntry { this._state = NotebookCellExecutionState.Executing; } + if (!this._didPause && updates.some(u => u.editType === CellExecutionUpdateType.ExecutionState && u.didPause)) { + this._didPause = true; + } + const edits = updates.map(update => updateToEdit(update, this.cellHandle)); this._applyExecutionEdits(edits); } @@ -332,10 +340,9 @@ class CellExecution implements ICellExecutionEntry { handle: this.cellHandle, internalMetadata: { lastRunSuccess: completionData.lastRunSuccess, - runStartTime: cellModel.internalMetadata.didPause ? null : cellModel.internalMetadata.runStartTime, - runEndTime: cellModel.internalMetadata.didPause ? null : completionData.runEndTime, - isPaused: false, - didPause: false + runStartTime: this._didPause ? null : cellModel.internalMetadata.runStartTime, + runEndTime: this._didPause ? null : completionData.runEndTime, + isPaused: false } }; this._applyExecutionEdits([edit]); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 50bdbb8ab3f45..d0a73845257f1 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -105,7 +105,6 @@ export interface NotebookCellInternalMetadata { runStartTimeAdjustment?: number; runEndTime?: number; isPaused?: boolean; - didPause?: boolean; } export interface NotebookCellCollapseState { diff --git a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts index a17d4b964a189..4b480ef8503bc 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts @@ -16,6 +16,7 @@ export interface ICellExecutionStateUpdate { editType: CellExecutionUpdateType.ExecutionState; executionOrder?: number; runStartTime?: number; + didPause?: boolean; } export interface ICellExecutionComplete { @@ -27,6 +28,7 @@ export interface ICellExecutionEntry { notebook: URI; cellHandle: number; state: NotebookCellExecutionState; + didPause: boolean; } export interface ICellExecutionStateChangedEvent { From 02d46ca95c7af2d87bb49a8e4974655df7f917f9 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Tue, 4 Jan 2022 11:29:01 -0800 Subject: [PATCH 0984/2210] Updating Layout State (#139043) * commit in buildable state * reloading in broken state * fixes * better zen mode handling --- src/vs/workbench/browser/layout.ts | 983 ++++++++---------- src/vs/workbench/browser/layoutState.ts | 274 +++++ src/vs/workbench/browser/workbench.ts | 10 +- .../services/layout/browser/layoutService.ts | 8 +- 4 files changed, 698 insertions(+), 577 deletions(-) create mode 100644 src/vs/workbench/browser/layoutState.ts diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 5bc7f0c3a78cc..9f7d134b55918 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -13,14 +13,14 @@ import { IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor' import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/windows/common/windows'; +import { getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -51,49 +51,42 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { AuxiliaryBarPart, AUXILIARYBAR_ENABLED } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - -type PanelAlignment = 'left' | 'center' | 'right' | 'justify'; - -export enum Settings { - ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', - STATUSBAR_VISIBLE = 'workbench.statusBar.visible', - - SIDEBAR_POSITION = 'workbench.sideBar.location', - PANEL_POSITION = 'workbench.panel.defaultLocation', - PANEL_OPENS_MAXIMIZED = 'workbench.panel.opensMaximized', - PANEL_ALIGNMENT = 'workbench.experimental.panel.alignment', - - ZEN_MODE_RESTORE = 'zenMode.restore', +import { LayoutStateKeys, LayoutStateModel, WorkbenchLayoutSettings } from 'vs/workbench/browser/layoutState'; + +interface IWorkbenchLayoutWindowRuntimeState { + fullscreen: boolean, + maximized: boolean, + hasFocus: boolean, + windowBorder: boolean, + menuBar: { + toggled: boolean + }, + zenMode: { + transitionDisposables: DisposableStore + } } -enum Storage { - SIDEBAR_HIDDEN = 'workbench.sidebar.hidden', - SIDEBAR_SIZE = 'workbench.sidebar.size', - - AUXILIARYBAR_HIDDEN = 'workbench.auxiliarybar.hidden', - AUXILIARYBAR_SIZE = 'workbench.auxiliarybar.size', - - PANEL_HIDDEN = 'workbench.panel.hidden', - PANEL_POSITION = 'workbench.panel.location', - PANEL_SIZE = 'workbench.panel.size', - PANEL_DIMENSION = 'workbench.panel.dimension', - PANEL_LAST_NON_MAXIMIZED_WIDTH = 'workbench.panel.lastNonMaximizedWidth', - PANEL_LAST_NON_MAXIMIZED_HEIGHT = 'workbench.panel.lastNonMaximizedHeight', - PANEL_LAST_IS_MAXIMIZED = 'workbench.panel.lastIsMaximized', - - EDITOR_HIDDEN = 'workbench.editor.hidden', - - ZEN_MODE_ENABLED = 'workbench.zenmode.active', - CENTERED_LAYOUT_ENABLED = 'workbench.centerededitorlayout.active', - - GRID_LAYOUT = 'workbench.grid.layout', - GRID_WIDTH = 'workbench.grid.width', - GRID_HEIGHT = 'workbench.grid.height', +interface IWorkbenchLayoutWindowInitializationState { + views: { + defaults: string[] | undefined + containerToRestore: { + sideBar?: string, + panel?: string, + auxiliaryBar?: string, + } + }, + editor: { + restoreEditors: boolean, + editorsToOpen: Promise | IUntypedEditorInput[] + } +} - MENU_VISIBILITY = 'window.menuBarVisibility' +interface IWorkbenchLayoutWindowState { + runtime: IWorkbenchLayoutWindowRuntimeState, + initialization: IWorkbenchLayoutWindowInitializationState, } -enum Classes { +enum WorkbenchLayoutClasses { SIDEBAR_HIDDEN = 'nosidebar', EDITOR_HIDDEN = 'noeditorarea', PANEL_HIDDEN = 'nopanel', @@ -103,6 +96,8 @@ enum Classes { WINDOW_BORDER = 'border' } +interface IInitialFilesToOpen { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } + export abstract class Layout extends Disposable implements IWorkbenchLayoutService { declare readonly _serviceBrand: undefined; @@ -155,6 +150,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly parts = new Map(); + private _initialized: boolean = false; private workbenchGrid!: SerializableGrid; private disposed: boolean | undefined; @@ -186,71 +182,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private logService!: ILogService; private telemetryService!: ITelemetryService; - protected readonly state = { - fullscreen: false, - maximized: false, - hasFocus: false, - windowBorder: false, - - menuBar: { - visibility: 'classic' as MenuBarVisibility, - toggled: false - }, - - activityBar: { - hidden: false - }, - - sideBar: { - hidden: false, - position: Position.LEFT, - width: 300, - viewletToRestore: undefined as string | undefined - }, - - editor: { - hidden: false, - centered: false, - restoreCentered: false, - restoreEditors: false, - editorsToOpen: [] as Promise | IUntypedEditorInput[] - }, - - panel: { - hidden: false, - position: Position.BOTTOM, - lastNonMaximizedWidth: 300, - lastNonMaximizedHeight: 300, - wasLastMaximized: false, - panelToRestore: undefined as string | undefined, - alignment: 'center' as PanelAlignment - }, - - auxiliaryBar: { - hidden: false, - panelToRestore: undefined as string | undefined - }, - - statusBar: { - hidden: false - }, - - views: { - defaults: undefined as (string[] | undefined) - }, - - zenMode: { - active: false, - restore: false, - transitionedToFullScreen: false, - transitionedToCenteredEditorLayout: false, - wasSideBarVisible: false, - wasPanelVisible: false, - wasAuxiliaryBarPartVisible: false, - transitionDisposables: new DisposableStore(), - setNotificationsFilter: false, - } - }; + private windowState!: IWorkbenchLayoutWindowState; + private stateModel!: LayoutStateModel; constructor( protected readonly parent: HTMLElement @@ -293,7 +226,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restore editor if hidden const showEditorIfHidden = () => { - if (this.state.editor.hidden) { + if (!this.isVisible(Parts.EDITOR_PART)) { this.toggleMaximizedPanel(); } }; @@ -306,17 +239,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }); // Revalidate center layout when active editor changes: diff editor quits centered mode. - this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.state.editor.centered))); - - // Configuration changes - this._register(this.configurationService.onDidChangeConfiguration(() => this.doUpdateLayoutConfiguration())); + this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); // Fullscreen changes this._register(onDidChangeFullscreen(() => this.onFullscreenChanged())); // Group changes - this._register(this.editorGroupService.onDidAddGroup(() => this.centerEditorLayout(this.state.editor.centered))); - this._register(this.editorGroupService.onDidRemoveGroup(() => this.centerEditorLayout(this.state.editor.centered))); + this._register(this.editorGroupService.onDidAddGroup(() => this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); + this._register(this.editorGroupService.onDidRemoveGroup(() => this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); // Prevent workbench from scrolling #55456 this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); @@ -334,16 +264,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private onMenubarToggled(visible: boolean) { - if (visible !== this.state.menuBar.toggled) { - this.state.menuBar.toggled = visible; + if (visible !== this.windowState.runtime.menuBar.toggled) { + this.windowState.runtime.menuBar.toggled = visible; + const menuBarVisibility = getMenuBarVisibility(this.configurationService); // The menu bar toggles the title bar in web because it does not need to be shown for window controls only - if (isWeb && this.state.menuBar.visibility === 'toggle') { + if (isWeb && menuBarVisibility === 'toggle') { this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } // The menu bar toggles the title bar in full screen for toggle and classic settings - else if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'classic')) { + else if (this.windowState.runtime.fullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) { this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } @@ -355,21 +286,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private onFullscreenChanged(): void { - this.state.fullscreen = isFullscreen(); + this.windowState.runtime.fullscreen = isFullscreen(); // Apply as CSS class - if (this.state.fullscreen) { - this.container.classList.add(Classes.FULLSCREEN); + if (this.windowState.runtime.fullscreen) { + this.container.classList.add(WorkbenchLayoutClasses.FULLSCREEN); } else { - this.container.classList.remove(Classes.FULLSCREEN); + this.container.classList.remove(WorkbenchLayoutClasses.FULLSCREEN); - if (this.state.zenMode.transitionedToFullScreen && this.state.zenMode.active) { + const zenModeExitInfo = this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_EXIT_INFO); + const zenModeActive = this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE); + if (zenModeExitInfo.transitionedToFullScreen && zenModeActive) { this.toggleZenMode(); } } // Change edge snapping accordingly - this.workbenchGrid.edgeSnapping = this.state.fullscreen; + this.workbenchGrid.edgeSnapping = this.windowState.runtime.fullscreen; // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService) === 'custom') { @@ -379,69 +312,36 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.updateWindowBorder(true); } - this._onDidChangeFullscreen.fire(this.state.fullscreen); + this._onDidChangeFullscreen.fire(this.windowState.runtime.fullscreen); } private onWindowFocusChanged(hasFocus: boolean): void { - if (this.state.hasFocus === hasFocus) { + if (this.windowState.runtime.hasFocus === hasFocus) { return; } - this.state.hasFocus = hasFocus; + this.windowState.runtime.hasFocus = hasFocus; this.updateWindowBorder(); } private doUpdateLayoutConfiguration(skipLayout?: boolean): void { - // Sidebar position - const newSidebarPositionValue = this.configurationService.getValue(Settings.SIDEBAR_POSITION); - const newSidebarPosition = (newSidebarPositionValue === 'right') ? Position.RIGHT : Position.LEFT; - if (newSidebarPosition !== this.getSideBarPosition()) { - this.setSideBarPosition(newSidebarPosition); - } - - // Panel position - this.updatePanelPosition(); - - - // Panel alignment - const newPanelAlignmentValue = this.configurationService.getValue(Settings.PANEL_ALIGNMENT) ?? 'center'; - if (newPanelAlignmentValue !== this.state.panel.alignment) { - this.setPanelAlignment(newPanelAlignmentValue, skipLayout); - } - - - if (!this.state.zenMode.active) { - - // Statusbar visibility - const newStatusbarHiddenValue = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); - if (newStatusbarHiddenValue !== this.state.statusBar.hidden) { - this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); - } - - // Activitybar visibility - const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); - if (newActivityBarHiddenValue !== this.state.activityBar.hidden) { - this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); - } - } - // Menubar visibility - const newMenubarVisibility = getMenuBarVisibility(this.configurationService); - this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); + this.updateMenubarVisibility(!!skipLayout); // Centered Layout - this.centerEditorLayout(this.state.editor.centered, skipLayout); + this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED), skipLayout); } private setSideBarPosition(position: Position): void { const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); const sideBar = this.getPart(Parts.SIDEBAR_PART); const auxiliaryBar = this.getPart(Parts.AUXILIARYBAR_PART); - const wasHidden = this.state.sideBar.hidden; const newPositionValue = (position === Position.LEFT) ? 'left' : 'right'; - const oldPositionValue = (this.state.sideBar.position === Position.LEFT) ? 'left' : 'right'; - this.state.sideBar.position = position; + const oldPositionValue = (position === Position.RIGHT) ? 'left' : 'right'; + const panelAlignment = this.getPanelAlignment(); + + this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON, position); // Adjust CSS const activityBarContainer = assertIsDefined(activityBar.getContainer()); @@ -461,14 +361,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi sideBar.updateStyles(); auxiliaryBar.updateStyles(); - // Layout - if (!wasHidden) { - this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView).width; - } - // Move activity bar, side bar, and side panel - this.adjustPartPositions(position, this.state.panel.alignment); - // this.layout(); + this.adjustPartPositions(position, panelAlignment); } private updateWindowBorder(skipLayout: boolean = false) { @@ -482,21 +376,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER); let windowBorder = false; - if (!this.state.fullscreen && !this.state.maximized && (activeBorder || inactiveBorder)) { + if (!this.windowState.runtime.fullscreen && !this.windowState.runtime.maximized && (activeBorder || inactiveBorder)) { windowBorder = true; // If the inactive color is missing, fallback to the active one - const borderColor = this.state.hasFocus ? activeBorder : inactiveBorder ?? activeBorder; + const borderColor = this.windowState.runtime.hasFocus ? activeBorder : inactiveBorder ?? activeBorder; this.container.style.setProperty('--window-border-color', borderColor?.toString() ?? 'transparent'); } - if (windowBorder === this.state.windowBorder) { + if (windowBorder === this.windowState.runtime.windowBorder) { return; } - this.state.windowBorder = windowBorder; + this.windowState.runtime.windowBorder = windowBorder; - this.container.classList.toggle(Classes.WINDOW_BORDER, windowBorder); + this.container.classList.toggle(WorkbenchLayoutClasses.WINDOW_BORDER, windowBorder); if (!skipLayout) { this.layout(); @@ -508,134 +402,150 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void { + this.stateModel = new LayoutStateModel(this.storageService, this.configurationService, this.parent); + this.stateModel.load(); - // Default Layout - this.applyDefaultLayout(this.environmentService, this.storageService); + // Both editor and panel should not be hidden on startup + if (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) && this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN)) { + this.stateModel.setRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN, false); + } - // Fullscreen - this.state.fullscreen = isFullscreen(); + this.stateModel.onDidChangeState(change => { + if (change.key === LayoutStateKeys.ACTIVITYBAR_HIDDEN) { + this.setActivityBarHidden(change.value as boolean); + } - // Menubar visibility - this.state.menuBar.visibility = getMenuBarVisibility(this.configurationService); + if (change.key === LayoutStateKeys.STATUSBAR_HIDDEN) { + this.setStatusBarHidden(change.value as boolean); + } - // Activity bar visibility - this.state.activityBar.hidden = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); + if (change.key === LayoutStateKeys.SIDEBAR_POSITON) { + this.setSideBarPosition(change.value as Position); + } - // Sidebar visibility - this.state.sideBar.hidden = this.storageService.getBoolean(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY); + if (change.key === LayoutStateKeys.PANEL_ALIGNMENT) { + this.setPanelAlignment(change.value as PanelAlignment); + } - // Sidebar position - this.state.sideBar.position = (this.configurationService.getValue(Settings.SIDEBAR_POSITION) === 'right') ? Position.RIGHT : Position.LEFT; + this.doUpdateLayoutConfiguration(); + }); - // Sidebar viewlet - if (!this.state.sideBar.hidden) { + // Window Initialization State + const initialFilesToOpen = this.getInitialFilesToOpen(); + const windowInitializationState: IWorkbenchLayoutWindowInitializationState = { + editor: { + restoreEditors: this.shouldRestoreEditors(this.contextService, initialFilesToOpen), + editorsToOpen: this.resolveEditorsToOpen(fileService, this.contextService, initialFilesToOpen), + }, + views: { + defaults: this.getDefaultLayoutViews(this.environmentService, this.storageService), + containerToRestore: {} + }, + }; + // Window Runtime State + const windowRuntimeState: IWorkbenchLayoutWindowRuntimeState = { + fullscreen: isFullscreen(), + hasFocus: this.hostService.hasFocus, + maximized: false, + windowBorder: false, + menuBar: { + toggled: false, + }, + zenMode: { + transitionDisposables: new DisposableStore(), + } + }; + + this.windowState = { + initialization: windowInitializationState, + runtime: windowRuntimeState, + }; + + // Sidebar View Container To Restore + if (this.isVisible(Parts.SIDEBAR_PART)) { // Only restore last viewlet if window was reloaded or we are in development mode - let viewletToRestore: string | undefined; + let viewContainerToRestore: string | undefined; if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow || isWeb) { - viewletToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); + viewContainerToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); } else { - viewletToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; + viewContainerToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; } - if (viewletToRestore) { - this.state.sideBar.viewletToRestore = viewletToRestore; + if (viewContainerToRestore) { + this.windowState.initialization.views.containerToRestore.sideBar = viewContainerToRestore; } else { - this.state.sideBar.hidden = true; // we hide sidebar if there is no viewlet to restore + this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN, true); } } - // Editor visibility - this.state.editor.hidden = this.storageService.getBoolean(Storage.EDITOR_HIDDEN, StorageScope.WORKSPACE, false); - - // Editor centered layout - this.state.editor.restoreCentered = this.storageService.getBoolean(Storage.CENTERED_LAYOUT_ENABLED, StorageScope.WORKSPACE, false); - - // Editors to open - this.state.editor.editorsToOpen = this.resolveEditorsToOpen(fileService, this.contextService); - - // Panel visibility - this.state.panel.hidden = this.storageService.getBoolean(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE, true); - - // Whether or not the panel was last maximized - this.state.panel.wasLastMaximized = this.storageService.getBoolean(Storage.PANEL_LAST_IS_MAXIMIZED, StorageScope.WORKSPACE, false); - - // Panel position - this.updatePanelPosition(); + // Panel View Container To Restore + if (this.isVisible(Parts.PANEL_PART)) { + let viewContainerToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id); - this.state.panel.alignment = this.configurationService.getValue(Settings.PANEL_ALIGNMENT) ?? 'center'; - - // Panel to restore - if (!this.state.panel.hidden) { - let panelToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id); - - if (panelToRestore) { - this.state.panel.panelToRestore = panelToRestore; + if (viewContainerToRestore) { + this.windowState.initialization.views.containerToRestore.panel = viewContainerToRestore; } else { - this.state.panel.hidden = true; // we hide panel if there is no panel to restore + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_HIDDEN, true); } } - // Auxiliary Bar visibility - this.state.auxiliaryBar.hidden = !this.configurationService.getValue(AUXILIARYBAR_ENABLED) || this.storageService.getBoolean(Storage.AUXILIARYBAR_HIDDEN, StorageScope.WORKSPACE, true); - // Auxiliary Panel to restore - if (!this.state.auxiliaryBar.hidden) { - let auxiliaryPanelToRestore = this.storageService.get(AuxiliaryBarPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id); + if (this.configurationService.getValue(AUXILIARYBAR_ENABLED) && this.isVisible(Parts.AUXILIARYBAR_PART)) { + let viewContainerToRestore = this.storageService.get(AuxiliaryBarPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id); - if (auxiliaryPanelToRestore) { - this.state.auxiliaryBar.panelToRestore = auxiliaryPanelToRestore; + if (viewContainerToRestore) { + this.windowState.initialization.views.containerToRestore.auxiliaryBar = viewContainerToRestore; } else { - this.state.auxiliaryBar.hidden = true; // we hide panel if there is no auxiliary panel to restore + this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN, true); } } - // Panel size before maximized - this.state.panel.lastNonMaximizedHeight = this.storageService.getNumber(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, StorageScope.GLOBAL, 300); - this.state.panel.lastNonMaximizedWidth = this.storageService.getNumber(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, StorageScope.GLOBAL, 300); - - // Statusbar visibility - this.state.statusBar.hidden = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); - - // Zen mode enablement - this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); - - this.state.hasFocus = this.hostService.hasFocus; + // Hide Auxiliary Bar if disabled + if (!this.configurationService.getValue(AUXILIARYBAR_ENABLED)) { + this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN, true); + } // Window border this.updateWindowBorder(true); } - private applyDefaultLayout(environmentService: IWorkbenchEnvironmentService, storageService: IStorageService) { + private getDefaultLayoutViews(environmentService: IWorkbenchEnvironmentService, storageService: IStorageService): string[] | undefined { const defaultLayout = environmentService.options?.defaultLayout; if (!defaultLayout) { - return; + return undefined; } if (!defaultLayout.force && !storageService.isNew(StorageScope.WORKSPACE)) { - return; + return undefined; } const { views } = defaultLayout; if (views?.length) { - this.state.views.defaults = views.map(view => view.id); + return views.map(view => view.id); } - } - private resolveEditorsToOpen(fileService: IFileService, contextService: IWorkspaceContextService): Promise | IUntypedEditorInput[] { - const initialFilesToOpen = this.getInitialFilesToOpen(); + return undefined; + } + private shouldRestoreEditors(contextService: IWorkspaceContextService, initialFilesToOpen: IInitialFilesToOpen | undefined): boolean { // Restore editors based on a set of rules: // - never when running in web on `tmp` scheme // - not when we have files to open, unless: // - always when `window.restoreWindows: preserve` if (isWeb && getVirtualWorkspaceScheme(contextService.getWorkspace()) === Schemas.tmp) { - this.state.editor.restoreEditors = false; + return false; } else { const forceRestoreEditors = this.configurationService.getValue('window.restoreWindows') === 'preserve'; - this.state.editor.restoreEditors = !!forceRestoreEditors || initialFilesToOpen === undefined; + return !!forceRestoreEditors || initialFilesToOpen === undefined; } + } + protected willRestoreEditors(): boolean { + return this.windowState.initialization.editor.restoreEditors; + } + + private resolveEditorsToOpen(fileService: IFileService, contextService: IWorkspaceContextService, initialFilesToOpen: IInitialFilesToOpen | undefined): Promise | IUntypedEditorInput[] { // Files to open, diff or create if (initialFilesToOpen) { @@ -677,7 +587,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private _openedDefaultEditors: boolean = false; get openedDefaultEditors() { return this._openedDefaultEditors; } - private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined { + private getInitialFilesToOpen(): IInitialFilesToOpen | undefined { // Check for editors from `defaultLayout` options first const defaultLayout = this.environmentService.options?.defaultLayout; @@ -745,10 +655,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // not need to await the editors from having // fully loaded. let editors: IUntypedEditorInput[]; - if (Array.isArray(this.state.editor.editorsToOpen)) { - editors = this.state.editor.editorsToOpen; + if (Array.isArray(this.windowState.initialization.editor.editorsToOpen)) { + editors = this.windowState.initialization.editor.editorsToOpen; } else { - editors = await this.state.editor.editorsToOpen; + editors = await this.windowState.initialization.editor.editorsToOpen; } let openEditorsPromise: Promise | undefined = undefined; @@ -773,7 +683,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restore default views (only when `IDefaultLayout` is provided) const restoreDefaultViewsPromise = (async () => { - if (this.state.views.defaults?.length) { + if (this.windowState.initialization.views.defaults?.length) { mark('code/willOpenDefaultViews'); const locationsRestored: { id: string; order: number; }[] = []; @@ -798,7 +708,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; }; - const defaultViews = [...this.state.views.defaults].reverse().map((v, index) => ({ id: v, order: index })); + const defaultViews = [...this.windowState.initialization.views.defaults].reverse().map((v, index) => ({ id: v, order: index })); let i = defaultViews.length; while (i) { @@ -823,17 +733,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // If we opened a view in the sidebar, stop any restore there if (locationsRestored[ViewContainerLocation.Sidebar]) { - this.state.sideBar.viewletToRestore = locationsRestored[ViewContainerLocation.Sidebar].id; + this.windowState.initialization.views.containerToRestore.sideBar = locationsRestored[ViewContainerLocation.Sidebar].id; } // If we opened a view in the panel, stop any restore there if (locationsRestored[ViewContainerLocation.Panel]) { - this.state.panel.panelToRestore = locationsRestored[ViewContainerLocation.Panel].id; + this.windowState.initialization.views.containerToRestore.panel = locationsRestored[ViewContainerLocation.Panel].id; } // If we opened a view in the auxiliary bar, stop any restore there if (locationsRestored[ViewContainerLocation.AuxiliaryBar]) { - this.state.auxiliaryBar.panelToRestore = locationsRestored[ViewContainerLocation.AuxiliaryBar].id; + this.windowState.initialization.views.containerToRestore.auxiliaryBar = locationsRestored[ViewContainerLocation.AuxiliaryBar].id; } mark('code/didOpenDefaultViews'); @@ -847,13 +757,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restoring views could mean that sidebar already // restored, as such we need to test again await restoreDefaultViewsPromise; - if (!this.state.sideBar.viewletToRestore) { + if (!this.windowState.initialization.views.containerToRestore.sideBar) { return; } mark('code/willRestoreViewlet'); - const viewlet = await this.paneCompositeService.openPaneComposite(this.state.sideBar.viewletToRestore, ViewContainerLocation.Sidebar); + const viewlet = await this.paneCompositeService.openPaneComposite(this.windowState.initialization.views.containerToRestore.sideBar, ViewContainerLocation.Sidebar); if (!viewlet) { await this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id, ViewContainerLocation.Sidebar); // fallback to default viewlet as needed } @@ -867,13 +777,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restoring views could mean that panel already // restored, as such we need to test again await restoreDefaultViewsPromise; - if (!this.state.panel.panelToRestore) { + if (!this.windowState.initialization.views.containerToRestore.panel) { return; } mark('code/willRestorePanel'); - const panel = await this.paneCompositeService.openPaneComposite(this.state.panel.panelToRestore, ViewContainerLocation.Panel); + const panel = await this.paneCompositeService.openPaneComposite(this.windowState.initialization.views.containerToRestore.panel, ViewContainerLocation.Panel); if (!panel) { await this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id, ViewContainerLocation.Panel); // fallback to default panel as needed } @@ -887,13 +797,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restoring views could mean that panel already // restored, as such we need to test again await restoreDefaultViewsPromise; - if (!this.state.auxiliaryBar.panelToRestore) { + if (!this.windowState.initialization.views.containerToRestore.auxiliaryBar) { return; } mark('code/willRestoreAuxiliaryBar'); - const panel = await this.paneCompositeService.openPaneComposite(this.state.auxiliaryBar.panelToRestore, ViewContainerLocation.AuxiliaryBar); + const panel = await this.paneCompositeService.openPaneComposite(this.windowState.initialization.views.containerToRestore.auxiliaryBar, ViewContainerLocation.AuxiliaryBar); if (!panel) { await this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id, ViewContainerLocation.AuxiliaryBar); // fallback to default panel as needed } @@ -902,12 +812,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })()); // Restore Zen Mode - if (this.state.zenMode.restore) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE) && getZenModeConfiguration(this.configurationService).restore) { this.toggleZenMode(false, true); } // Restore Editor Center Mode - if (this.state.editor.restoreCentered) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)) { this.centerEditorLayout(true, true); } @@ -923,13 +833,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }); } - private updatePanelPosition() { - const defaultPanelPosition = this.configurationService.getValue(Settings.PANEL_POSITION); - const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition); - - this.state.panel.position = positionFromString(panelPosition || defaultPanelPosition); - } - registerPart(part: Part): void { this.parts.set(part.getId(), part); } @@ -993,77 +896,89 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } getContainer(part: Parts): HTMLElement | undefined { - switch (part) { - case Parts.TITLEBAR_PART: - return this.getPart(Parts.TITLEBAR_PART).getContainer(); - case Parts.BANNER_PART: - return this.getPart(Parts.BANNER_PART).getContainer(); - case Parts.ACTIVITYBAR_PART: - return this.getPart(Parts.ACTIVITYBAR_PART).getContainer(); - case Parts.SIDEBAR_PART: - return this.getPart(Parts.SIDEBAR_PART).getContainer(); - case Parts.PANEL_PART: - return this.getPart(Parts.PANEL_PART).getContainer(); - case Parts.AUXILIARYBAR_PART: - return this.getPart(Parts.AUXILIARYBAR_PART).getContainer(); - case Parts.EDITOR_PART: - return this.getPart(Parts.EDITOR_PART).getContainer(); - case Parts.STATUSBAR_PART: - return this.getPart(Parts.STATUSBAR_PART).getContainer(); + if (!this.parts.get(part)) { + return undefined; } + + return this.getPart(part).getContainer(); } isVisible(part: Parts): boolean { + if (this._initialized) { + switch (part) { + case Parts.TITLEBAR_PART: + return this.workbenchGrid.isViewVisible(this.titleBarPartView); + case Parts.SIDEBAR_PART: + return !this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN); + case Parts.PANEL_PART: + return !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN); + case Parts.AUXILIARYBAR_PART: + return !this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN); + case Parts.STATUSBAR_PART: + return !this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN); + case Parts.ACTIVITYBAR_PART: + return !this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN); + case Parts.EDITOR_PART: + return !this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN); + default: + return false; // any other part cannot be hidden + } + } + switch (part) { case Parts.TITLEBAR_PART: - // Using the native title bar, don't ever show the custom one - if (getTitleBarStyle(this.configurationService) === 'native') { - return false; - } - - // macOS desktop does not need a title bar when full screen - if (isMacintosh && isNative) { - return !this.state.fullscreen; - } - - // non-fullscreen native must show the title bar - if (isNative && !this.state.fullscreen) { - return true; - } - - // remaining behavior is based on menubar visibility - switch (this.state.menuBar.visibility) { - case 'classic': - return !this.state.fullscreen || this.state.menuBar.toggled; - case 'compact': - case 'hidden': - return false; - case 'toggle': - return this.state.menuBar.toggled; - case 'visible': - return true; - default: - return isWeb ? false : !this.state.fullscreen || this.state.menuBar.toggled; - } + return this.shouldShowTitleBar(); case Parts.SIDEBAR_PART: - return !this.state.sideBar.hidden; + return !this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN); case Parts.PANEL_PART: - return !this.state.panel.hidden; + return !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN); case Parts.AUXILIARYBAR_PART: - return !!this.configurationService.getValue(AUXILIARYBAR_ENABLED) && !this.state.auxiliaryBar.hidden; + return !this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN); case Parts.STATUSBAR_PART: - return !this.state.statusBar.hidden; + return !this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN); case Parts.ACTIVITYBAR_PART: - return !this.state.activityBar.hidden; + return !this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN); case Parts.EDITOR_PART: - return !this.state.editor.hidden; + return !this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN); default: - return true; // any other part cannot be hidden + return false; // any other part cannot be hidden + } + } + + private shouldShowTitleBar(): boolean { + // Using the native title bar, don't ever show the custom one + if (getTitleBarStyle(this.configurationService) === 'native') { + return false; + } + + // macOS desktop does not need a title bar when full screen + if (isMacintosh && isNative) { + return !this.windowState.runtime.fullscreen; + } + + // non-fullscreen native must show the title bar + if (isNative && !this.windowState.runtime.fullscreen) { + return true; + } + + // remaining behavior is based on menubar visibility + switch (getMenuBarVisibility(this.configurationService)) { + case 'classic': + return !this.windowState.runtime.fullscreen || this.windowState.runtime.menuBar.toggled; + case 'compact': + case 'hidden': + return false; + case 'toggle': + return this.windowState.runtime.menuBar.toggled; + case 'visible': + return true; + default: + return isWeb ? false : !this.windowState.runtime.fullscreen || this.windowState.runtime.menuBar.toggled; } } focus(): void { - this.editorGroupService.activeGroup.focus(); + this.focusPart(Parts.EDITOR_PART); } getDimension(part: Parts): Dimension | undefined { @@ -1071,7 +986,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } getMaximumEditorDimensions(): Dimension { - const isColumn = this.state.panel.position === Position.RIGHT || this.state.panel.position === Position.LEFT; + const panelPosition = this.getPanelPosition(); + const isColumn = panelPosition === Position.RIGHT || panelPosition === Position.LEFT; const takenWidth = (this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) + (this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) + @@ -1090,8 +1006,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } toggleZenMode(skipLayout?: boolean, restoring = false): void { - this.state.zenMode.active = !this.state.zenMode.active; - this.state.zenMode.transitionDisposables.clear(); + this.stateModel.setRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE, !this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE)); + this.windowState.runtime.zenMode.transitionDisposables.clear(); const setLineNumbers = (lineNumbers?: LineNumbersType) => { const setEditorLineNumbers = (editor: IEditor) => { @@ -1122,26 +1038,20 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Check if zen mode transitioned to full screen and if now we are out of zen mode // -> we need to go out of full screen (same goes for the centered editor layout) let toggleFullScreen = false; + const config = getZenModeConfiguration(this.configurationService); + const zenModeExitInfo = this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_EXIT_INFO); // Zen Mode Active - if (this.state.zenMode.active) { - const config: { - fullScreen: boolean; - centerLayout: boolean; - hideTabs: boolean; - hideActivityBar: boolean; - hideStatusBar: boolean; - hideLineNumbers: boolean; - silentNotifications: boolean; - } = this.configurationService.getValue('zenMode'); - - toggleFullScreen = !this.state.fullscreen && config.fullScreen && !isIOS; - - this.state.zenMode.transitionedToFullScreen = restoring ? config.fullScreen : toggleFullScreen; - this.state.zenMode.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; - this.state.zenMode.wasSideBarVisible = this.isVisible(Parts.SIDEBAR_PART); - this.state.zenMode.wasPanelVisible = this.isVisible(Parts.PANEL_PART); - this.state.zenMode.wasAuxiliaryBarPartVisible = this.isVisible(Parts.AUXILIARYBAR_PART); + if (this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE)) { + + toggleFullScreen = !this.windowState.runtime.fullscreen && config.fullScreen && !isIOS; + + zenModeExitInfo.transitionedToFullScreen = restoring ? config.fullScreen : toggleFullScreen; + zenModeExitInfo.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; + zenModeExitInfo.wasVisible.sideBar = this.isVisible(Parts.SIDEBAR_PART); + zenModeExitInfo.wasVisible.panel = this.isVisible(Parts.PANEL_PART); + zenModeExitInfo.wasVisible.auxiliaryBar = this.isVisible(Parts.AUXILIARYBAR_PART); + this.stateModel.setRuntimeValue(LayoutStateKeys.ZEN_MODE_EXIT_INFO, zenModeExitInfo); this.setPanelHidden(true, true); this.setAuxiliaryBarHidden(true, true); @@ -1157,21 +1067,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (config.hideLineNumbers) { setLineNumbers('off'); - this.state.zenMode.transitionDisposables.add(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); + this.windowState.runtime.zenMode.transitionDisposables.add(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); } if (config.hideTabs && this.editorGroupService.partOptions.showTabs) { - this.state.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); + this.windowState.runtime.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); } - this.state.zenMode.setNotificationsFilter = config.silentNotifications; if (config.silentNotifications) { this.notificationService.setFilter(NotificationsFilter.ERROR); } - this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => { - const silentNotificationsKey = 'zenMode.silentNotifications'; - if (e.affectsConfiguration(silentNotificationsKey)) { - const filter = this.configurationService.getValue(silentNotificationsKey) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; + this.windowState.runtime.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS)) { + const filter = this.configurationService.getValue(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; this.notificationService.setFilter(filter); } })); @@ -1183,33 +1091,38 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Zen Mode Inactive else { - if (this.state.zenMode.wasPanelVisible) { + if (zenModeExitInfo.wasVisible.panel) { this.setPanelHidden(false, true); } - if (this.state.zenMode.wasAuxiliaryBarPartVisible) { + if (zenModeExitInfo.wasVisible.auxiliaryBar) { this.setAuxiliaryBarHidden(false, true); } - if (this.state.zenMode.wasSideBarVisible) { + if (zenModeExitInfo.wasVisible.sideBar) { this.setSideBarHidden(false, true); } - if (this.state.zenMode.transitionedToCenteredEditorLayout) { + if (!this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN, true)) { + this.setActivityBarHidden(false, true); + } + + if (!this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN, true)) { + this.setStatusBarHidden(false, true); + } + + if (zenModeExitInfo.transitionedToCenteredEditorLayout) { this.centerEditorLayout(false, true); } setLineNumbers(); - // Status bar and activity bar visibility come from settings -> update their visibility. - this.doUpdateLayoutConfiguration(true); - this.focus(); - if (this.state.zenMode.setNotificationsFilter) { - this.notificationService.setFilter(NotificationsFilter.OFF); - } - toggleFullScreen = this.state.zenMode.transitionedToFullScreen && this.state.fullscreen; + // Clear notifications filter + this.notificationService.setFilter(NotificationsFilter.OFF); + + toggleFullScreen = zenModeExitInfo.transitionedToFullScreen && this.windowState.runtime.fullscreen; } if (!skipLayout) { @@ -1221,33 +1134,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Event - this._onDidChangeZenMode.fire(this.state.zenMode.active); - - // State - if (this.state.zenMode.active) { - this.storageService.store(Storage.ZEN_MODE_ENABLED, true, StorageScope.WORKSPACE, StorageTarget.USER); - - // Exit zen mode on shutdown unless configured to keep - this.state.zenMode.transitionDisposables.add(this.storageService.onWillSaveState(e => { - if (e.reason === WillSaveStateReason.SHUTDOWN && this.state.zenMode.active) { - if (!this.configurationService.getValue(Settings.ZEN_MODE_RESTORE)) { - this.toggleZenMode(true); // We will not restore zen mode, need to clear all zen mode state changes - } - } - })); - } else { - this.storageService.remove(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE); - } + this._onDidChangeZenMode.fire(this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE)); } private setStatusBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.state.statusBar.hidden = hidden; + this.stateModel.setRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN, hidden); // Adjust CSS if (hidden) { - this.container.classList.add(Classes.STATUSBAR_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.STATUSBAR_HIDDEN); } else { - this.container.classList.remove(Classes.STATUSBAR_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.STATUSBAR_HIDDEN); } // Propagate to grid @@ -1295,7 +1192,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.container.prepend(workbenchGrid.element); this.container.setAttribute('role', 'application'); this.workbenchGrid = workbenchGrid; - this.workbenchGrid.edgeSnapping = this.state.fullscreen; + this.workbenchGrid.edgeSnapping = this.windowState.runtime.fullscreen; for (const part of [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar, auxiliaryBarPart]) { this._register(part.onDidVisibilityChange((visible) => { @@ -1313,38 +1210,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } this._register(this.storageService.onWillSaveState(() => { - const grid = this.workbenchGrid as SerializableGrid; - - const sideBarSize = this.state.sideBar.hidden - ? grid.getViewCachedVisibleSize(this.sideBarPartView) - : grid.getViewSize(this.sideBarPartView).width; - - this.storageService.store(Storage.SIDEBAR_SIZE, sideBarSize, StorageScope.GLOBAL, StorageTarget.MACHINE); - - const auxiliaryBarSize = this.state.auxiliaryBar.hidden - ? grid.getViewCachedVisibleSize(this.auxiliaryBarPartView) - : grid.getViewSize(this.auxiliaryBarPartView).width; - - this.storageService.store(Storage.AUXILIARYBAR_SIZE, auxiliaryBarSize, StorageScope.GLOBAL, StorageTarget.MACHINE); - - const panelSize = this.state.panel.hidden - ? grid.getViewCachedVisibleSize(this.panelPartView) - : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); - - this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL, StorageTarget.MACHINE); - this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL, StorageTarget.MACHINE); - - // Remember last panel size for both dimensions - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, this.state.panel.lastNonMaximizedHeight, StorageScope.GLOBAL, StorageTarget.MACHINE); - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, this.state.panel.lastNonMaximizedWidth, StorageScope.GLOBAL, StorageTarget.MACHINE); - - const gridSize = grid.getViewSize(); - this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL, StorageTarget.MACHINE); - this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL, StorageTarget.MACHINE); + // save all serializable state + this.saveState(true, true); })); } - getClientArea(): Dimension { + private saveState(global: boolean, workspace: boolean) { + // implement save state + } + + private getClientArea(): Dimension { return getClientArea(this.parent); } @@ -1358,6 +1233,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout the grid widget this.workbenchGrid.layout(this._dimension.width, this._dimension.height); + this._initialized = true; // Emit as event this._onDidLayout.fire(this._dimension); @@ -1365,13 +1241,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } isEditorLayoutCentered(): boolean { - return this.state.editor.centered; + return this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED); } centerEditorLayout(active: boolean, skipLayout?: boolean): void { - this.state.editor.centered = active; - - this.storageService.store(Storage.CENTERED_LAYOUT_ENABLED, active, StorageScope.WORKSPACE, StorageTarget.USER); + active = false; + this.stateModel.setRuntimeValue(LayoutStateKeys.EDITOR_CENTERED, active); let smartActive = active; const activeEditor = this.editorService.activeEditor; @@ -1400,7 +1275,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - this._onDidChangeCenteredLayout.fire(this.state.editor.centered); + this._onDidChangeCenteredLayout.fire(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)); } resizePart(part: Parts, sizeChangeWidth: number, sizeChangeHeight: number): void { @@ -1473,9 +1348,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.state.activityBar.hidden = hidden; - // Propagate to grid + this.stateModel.setRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN, hidden); this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); } @@ -1484,49 +1358,43 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private setEditorHidden(hidden: boolean, skipLayout?: boolean): void { - this.state.editor.hidden = hidden; + this.stateModel.setRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN, hidden); // Adjust CSS if (hidden) { - this.container.classList.add(Classes.EDITOR_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.EDITOR_HIDDEN); } else { - this.container.classList.remove(Classes.EDITOR_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.EDITOR_HIDDEN); } // Propagate to grid this.workbenchGrid.setViewVisible(this.editorPartView, !hidden); - // Remember in settings - if (hidden) { - this.storageService.store(Storage.EDITOR_HIDDEN, true, StorageScope.WORKSPACE, StorageTarget.USER); - } else { - this.storageService.remove(Storage.EDITOR_HIDDEN, StorageScope.WORKSPACE); - } - // The editor and panel cannot be hidden at the same time - if (hidden && this.state.panel.hidden) { + if (hidden && !this.isVisible(Parts.PANEL_PART)) { this.setPanelHidden(false, true); } } getLayoutClasses(): string[] { return coalesce([ - this.state.sideBar.hidden ? Classes.SIDEBAR_HIDDEN : undefined, - this.state.editor.hidden ? Classes.EDITOR_HIDDEN : undefined, - this.state.panel.hidden ? Classes.PANEL_HIDDEN : undefined, - this.state.statusBar.hidden ? Classes.STATUSBAR_HIDDEN : undefined, - this.state.fullscreen ? Classes.FULLSCREEN : undefined + !this.isVisible(Parts.SIDEBAR_PART) ? WorkbenchLayoutClasses.SIDEBAR_HIDDEN : undefined, + !this.isVisible(Parts.EDITOR_PART) ? WorkbenchLayoutClasses.EDITOR_HIDDEN : undefined, + !this.isVisible(Parts.PANEL_PART) ? WorkbenchLayoutClasses.PANEL_HIDDEN : undefined, + !this.isVisible(Parts.AUXILIARYBAR_PART) ? WorkbenchLayoutClasses.AUXILIARYBAR_HIDDEN : undefined, + !this.isVisible(Parts.STATUSBAR_PART) ? WorkbenchLayoutClasses.STATUSBAR_HIDDEN : undefined, + this.windowState.runtime.fullscreen ? WorkbenchLayoutClasses.FULLSCREEN : undefined ]); } private setSideBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.state.sideBar.hidden = hidden; + this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN, hidden); // Adjust CSS if (hidden) { - this.container.classList.add(Classes.SIDEBAR_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.SIDEBAR_HIDDEN); } else { - this.container.classList.remove(Classes.SIDEBAR_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.SIDEBAR_HIDDEN); } // If sidebar becomes hidden, also hide the current active Viewlet if any @@ -1555,14 +1423,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Propagate to grid this.workbenchGrid.setViewVisible(this.sideBarPartView, !hidden); - - // Remember in settings - const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; - if (hidden !== defaultHidden) { - this.storageService.store(Storage.SIDEBAR_HIDDEN, hidden ? 'true' : 'false', StorageScope.WORKSPACE, StorageTarget.USER); - } else { - this.storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); - } } private hasViews(id: string): boolean { @@ -1583,8 +1443,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Move activity bar, side bar, and side panel const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); - const preMoveSideBarSize = this.state.sideBar.hidden ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width; - const preMoveAuxiliaryBarSize = this.state.auxiliaryBar.hidden ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) ?? this.auxiliaryBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; + const preMoveSideBarSize = !this.isVisible(Parts.SIDEBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width; + const preMoveAuxiliaryBarSize = !this.isVisible(Parts.AUXILIARYBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) ?? this.auxiliaryBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; if (sideBarPosition === Position.LEFT) { this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, 0]); @@ -1622,33 +1482,34 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private setPanelAlignment(alignment: PanelAlignment, skipLayout?: boolean): void { - this.state.panel.alignment = alignment; - // Panel alignment only applies to a panel in the bottom position - if (this.state.panel.position !== Position.BOTTOM) { + if (this.getPanelPosition() !== Position.BOTTOM) { return; } - this.adjustPartPositions(this.state.sideBar.position, alignment); + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT, alignment); + + this.adjustPartPositions(this.getSideBarPosition(), alignment); } private setPanelHidden(hidden: boolean, skipLayout?: boolean): void { - const wasHidden = this.state.panel.hidden; - this.state.panel.hidden = hidden; - // Return if not initialized fully #105480 if (!this.workbenchGrid) { return; } + const wasHidden = !this.isVisible(Parts.PANEL_PART); + + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_HIDDEN, hidden); + const isPanelMaximized = this.isPanelMaximized(); const panelOpensMaximized = this.panelOpensMaximized(); // Adjust CSS if (hidden) { - this.container.classList.add(Classes.PANEL_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.PANEL_HIDDEN); } else { - this.container.classList.remove(Classes.PANEL_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.PANEL_HIDDEN); } // If panel part becomes hidden, also hide the current active panel if any @@ -1688,6 +1549,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Propagate layout changes to grid this.workbenchGrid.setViewVisible(this.panelPartView, !hidden); + // If in process of showing, toggle whether or not panel is maximized if (!hidden) { if (!skipLayout && isPanelMaximized !== panelOpensMaximized) { @@ -1696,22 +1558,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } else { // If in process of hiding, remember whether the panel is maximized or not - this.state.panel.wasLastMaximized = isPanelMaximized; - } - // Remember in settings - if (!hidden) { - this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE, StorageTarget.USER); - } - else { - this.storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE); - - // Remember this setting only when panel is hiding - if (this.state.panel.wasLastMaximized) { - this.storageService.store(Storage.PANEL_LAST_IS_MAXIMIZED, true, StorageScope.WORKSPACE, StorageTarget.USER); - } - else { - this.storageService.remove(Storage.PANEL_LAST_IS_MAXIMIZED, StorageScope.WORKSPACE); - } + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_WAS_LAST_MAXIMIZED, isPanelMaximized); } if (focusEditor) { @@ -1721,30 +1568,34 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi toggleMaximizedPanel(): void { const size = this.workbenchGrid.getViewSize(this.panelPartView); - if (!this.isPanelMaximized()) { - if (!this.state.panel.hidden) { - if (this.state.panel.position === Position.BOTTOM) { - this.state.panel.lastNonMaximizedHeight = size.height; - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, this.state.panel.lastNonMaximizedHeight, StorageScope.GLOBAL, StorageTarget.MACHINE); + const panelPosition = this.getPanelPosition(); + const isMaximized = this.isPanelMaximized(); + if (!isMaximized) { + if (this.isVisible(Parts.PANEL_PART)) { + if (panelPosition === Position.BOTTOM) { + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height); } else { - this.state.panel.lastNonMaximizedWidth = size.width; - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, this.state.panel.lastNonMaximizedWidth, StorageScope.GLOBAL, StorageTarget.MACHINE); + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH, size.width); } } this.setEditorHidden(true); } else { this.setEditorHidden(false); - this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.lastNonMaximizedWidth, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.lastNonMaximizedHeight : size.height }); + this.workbenchGrid.resizeView(this.panelPartView, { + width: panelPosition === Position.BOTTOM ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), + height: panelPosition === Position.BOTTOM ? this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT) : size.height + }); } + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_WAS_LAST_MAXIMIZED, !isMaximized); } /** * Returns whether or not the panel opens maximized */ private panelOpensMaximized() { - const panelOpensMaximized = panelOpensMaximizedFromString(this.configurationService.getValue(Settings.PANEL_OPENS_MAXIMIZED)); - const panelLastIsMaximized = this.state.panel.wasLastMaximized; + const panelOpensMaximized = panelOpensMaximizedFromString(this.configurationService.getValue(WorkbenchLayoutSettings.PANEL_OPENS_MAXIMIZED)); + const panelLastIsMaximized = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_WAS_LAST_MAXIMIZED); return panelOpensMaximized === PanelOpensMaximizedOptions.ALWAYS || (panelOpensMaximized === PanelOpensMaximizedOptions.REMEMBER_LAST && panelLastIsMaximized); } @@ -1754,13 +1605,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return; } - this.state.auxiliaryBar.hidden = hidden; + this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN, hidden); // Adjust CSS if (hidden) { - this.container.classList.add(Classes.AUXILIARYBAR_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.AUXILIARYBAR_HIDDEN); } else { - this.container.classList.remove(Classes.AUXILIARYBAR_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.AUXILIARYBAR_HIDDEN); } // If auxiliary bar becomes hidden, also hide the current active pane composite if any @@ -1796,14 +1647,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Propagate to grid this.workbenchGrid.setViewVisible(this.auxiliaryBarPartView, !hidden); - - // Remember in settings - const defaultHidden = true; - if (hidden !== defaultHidden) { - this.storageService.store(Storage.AUXILIARYBAR_HIDDEN, hidden ? 'true' : 'false', StorageScope.WORKSPACE, StorageTarget.USER); - } else { - this.storageService.remove(Storage.AUXILIARYBAR_HIDDEN, StorageScope.WORKSPACE); - } } setPartHidden(hidden: boolean, part: Parts): void { @@ -1824,38 +1667,35 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } hasWindowBorder(): boolean { - return this.state.windowBorder; + return this.windowState.runtime.windowBorder; } getWindowBorderWidth(): number { - return this.state.windowBorder ? 2 : 0; + return this.windowState.runtime.windowBorder ? 2 : 0; } getWindowBorderRadius(): string | undefined { - return this.state.windowBorder && isMacintosh ? '5px' : undefined; + return this.windowState.runtime.windowBorder && isMacintosh ? '5px' : undefined; } isPanelMaximized(): boolean { - return this.state.editor.hidden; + return !this.isVisible(Parts.EDITOR_PART); } getSideBarPosition(): Position { - return this.state.sideBar.position; + return this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON); } - setMenubarVisibility(visibility: MenuBarVisibility, skipLayout: boolean): void { - if (this.state.menuBar.visibility !== visibility) { - this.state.menuBar.visibility = visibility; - - // Layout - if (!skipLayout && this.workbenchGrid) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); - } - } + private getPanelAlignment(): PanelAlignment { + return this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT); } - getMenubarVisibility(): MenuBarVisibility { - return this.state.menuBar.visibility; + updateMenubarVisibility(skipLayout: boolean): void { + // Layout + const shouldShowTitleBar = this.shouldShowTitleBar(); + if (!skipLayout && this.workbenchGrid && shouldShowTitleBar !== this.isVisible(Parts.TITLEBAR_PART)) { + this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowTitleBar); + } } toggleMenuBar(): void { @@ -1871,25 +1711,24 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi newVisibilityValue = 'classic'; } - this.configurationService.updateValue(Storage.MENU_VISIBILITY, newVisibilityValue); + this.configurationService.updateValue('window.menuBarVisibility', newVisibilityValue); } getPanelPosition(): Position { - return this.state.panel.position; + return this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION); } setPanelPosition(position: Position): void { - if (this.state.panel.hidden) { + if (!this.isVisible(Parts.PANEL_PART)) { this.setPanelHidden(false); } + const panelPart = this.getPart(Parts.PANEL_PART); - const oldPositionValue = positionToString(this.state.panel.position); + const oldPositionValue = positionToString(this.getPanelPosition()); const newPositionValue = positionToString(position); - this.state.panel.position = position; - // Save panel position - this.storageService.store(Storage.PANEL_POSITION, newPositionValue, StorageScope.WORKSPACE, StorageTarget.USER); + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_POSITION, position); // Adjust CSS const panelContainer = assertIsDefined(panelPart.getContainer()); @@ -1903,25 +1742,27 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const size = this.workbenchGrid.getViewSize(this.panelPartView); const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView); + const editorHidden = !this.isVisible(Parts.EDITOR_PART); + // Save last non-maximized size for panel before move - if (newPositionValue !== oldPositionValue && !this.state.editor.hidden) { + if (newPositionValue !== oldPositionValue && !editorHidden) { // Save the current size of the panel for the new orthogonal direction // If moving down, save the width of the panel // Otherwise, save the height of the panel if (position === Position.BOTTOM) { - this.state.panel.lastNonMaximizedWidth = size.width; + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH, size.width); } else if (positionFromString(oldPositionValue) === Position.BOTTOM) { - this.state.panel.lastNonMaximizedHeight = size.height; + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height); } } if (position === Position.BOTTOM) { - this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : this.state.panel.lastNonMaximizedHeight, this.editorPartView, Direction.Down); + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.height : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT), this.editorPartView, Direction.Down); } else if (position === Position.RIGHT) { - this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Right); + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), this.editorPartView, Direction.Right); } else { - this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Left); + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), this.editorPartView, Direction.Left); } // Reset sidebar to original size before shifting the panel @@ -1931,15 +1772,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } isWindowMaximized() { - return this.state.maximized; + return this.windowState.runtime.maximized; } updateWindowMaximizedState(maximized: boolean) { - if (this.state.maximized === maximized) { + if (this.windowState.runtime.maximized === maximized) { return; } - this.state.maximized = maximized; + this.windowState.runtime.maximized = maximized; this.updateWindowBorder(); this._onDidChangeWindowMaximized.fire(maximized); @@ -1973,7 +1814,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return undefined; } - private arrangeEditorNodes(nodes: { editor: ISerializedNode, sideBar?: ISerializedNode, auxiliaryBar?: ISerializedNode }, availableHeight: number, availableWidth: number): ISerializedNode { if (!nodes.sideBar && !nodes.auxiliaryBar) { nodes.editor.size = availableHeight; @@ -1983,23 +1823,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const result = [nodes.editor]; nodes.editor.size = availableWidth; if (nodes.sideBar) { - if (this.state.sideBar.position === Position.LEFT) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.LEFT) { result.splice(0, 0, nodes.sideBar); } else { result.push(nodes.sideBar); } - nodes.editor.size -= this.state.sideBar.hidden ? 0 : nodes.sideBar.size; + nodes.editor.size -= this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) ? 0 : nodes.sideBar.size; } if (nodes.auxiliaryBar) { - if (this.state.sideBar.position === Position.RIGHT) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.RIGHT) { result.splice(0, 0, nodes.auxiliaryBar); } else { result.push(nodes.auxiliaryBar); } - nodes.editor.size -= this.state.auxiliaryBar.hidden ? 0 : nodes.auxiliaryBar.size; + nodes.editor.size -= this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) ? 0 : nodes.auxiliaryBar.size; } return { @@ -2010,22 +1850,22 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private arrangeMiddleSectionNodes(nodes: { editor: ISerializedNode, panel: ISerializedNode, activityBar: ISerializedNode, sideBar: ISerializedNode, auxiliaryBar: ISerializedNode }, availableWidth: number, availableHeight: number): ISerializedNode[] { - const activityBarSize = this.state.activityBar.hidden ? 0 : nodes.activityBar.size; - const sideBarSize = this.state.sideBar.hidden ? 0 : nodes.sideBar.size; - const auxiliaryBarSize = this.state.auxiliaryBar.hidden ? 0 : nodes.auxiliaryBar.size; - const panelSize = this.state.panel.hidden ? 0 : nodes.panel.size; + const activityBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN) ? 0 : nodes.activityBar.size; + const sideBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) ? 0 : nodes.sideBar.size; + const auxiliaryBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) ? 0 : nodes.auxiliaryBar.size; + const panelSize = this.stateModel.getInitializationValue(LayoutStateKeys.PANEL_SIZE) ? 0 : nodes.panel.size; const result = [] as ISerializedNode[]; - if (this.state.panel.position !== Position.BOTTOM) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) !== Position.BOTTOM) { result.push(nodes.editor); nodes.editor.size = availableWidth - activityBarSize - sideBarSize - panelSize - auxiliaryBarSize; - if (this.state.panel.position === Position.RIGHT) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) === Position.RIGHT) { result.push(nodes.panel); } else { result.splice(0, 0, nodes.panel); } - if (this.state.sideBar.position === Position.LEFT) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.LEFT) { result.push(nodes.auxiliaryBar); result.splice(0, 0, nodes.sideBar); result.splice(0, 0, nodes.activityBar); @@ -2035,8 +1875,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi result.push(nodes.activityBar); } } else { - const panelAlignment = this.state.panel.alignment; - const sideBarPosition = this.state.sideBar.position; + const panelAlignment = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT); + const sideBarPosition = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON); const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); @@ -2078,14 +1918,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private createGridDescriptor(): ISerializedGrid { - const workbenchDimensions = this.getClientArea(); - const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); - const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); - const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300)); - const auxiliaryBarPartSize = this.storageService.getNumber(Storage.AUXILIARYBAR_SIZE, StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300)); - const panelDimension = positionFromString(this.storageService.get(Storage.PANEL_DIMENSION, StorageScope.GLOBAL, 'bottom')); - const fallbackPanelSize = this.state.panel.position === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; - const panelSize = panelDimension === this.state.panel.position ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, fallbackPanelSize) : fallbackPanelSize; + const { width, height } = this.stateModel.getInitializationValue(LayoutStateKeys.GRID_SIZE); + const sideBarSize = this.stateModel.getInitializationValue(LayoutStateKeys.SIDEBAR_SIZE); + const auxiliaryBarPartSize = this.stateModel.getInitializationValue(LayoutStateKeys.AUXILIARYBAR_SIZE); + const panelSize = this.stateModel.getInitializationValue(LayoutStateKeys.PANEL_SIZE); const titleBarHeight = this.titleBarPartView.minimumHeight; const bannerHeight = this.bannerPartView.minimumHeight; @@ -2097,14 +1933,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi type: 'leaf', data: { type: Parts.ACTIVITYBAR_PART }, size: activityBarWidth, - visible: !this.state.activityBar.hidden + visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN) }; const sideBarNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.SIDEBAR_PART }, size: sideBarSize, - visible: !this.state.sideBar.hidden + visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) }; const auxiliaryBarNode: ISerializedLeafNode = { @@ -2118,14 +1954,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi type: 'leaf', data: { type: Parts.EDITOR_PART }, size: 0, // Update based on sibling sizes - visible: !this.state.editor.hidden + visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN) }; const panelNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.PANEL_PART }, size: panelSize, - visible: !this.state.panel.hidden + visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) }; @@ -2163,7 +1999,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi type: 'leaf', data: { type: Parts.STATUSBAR_PART }, size: statusBarHeight, - visible: !this.state.statusBar.hidden + visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN) } ] }, @@ -2193,13 +2029,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }; const layoutDescriptor: StartupLayoutEvent = { - activityBarVisible: !this.state.auxiliaryBar.hidden, - sideBarVisible: !this.state.sideBar.hidden, - auxiliaryBarVisible: !this.state.auxiliaryBar.hidden, - panelVisible: !this.state.panel.hidden, - statusbarVisible: !this.state.statusBar.hidden, - sideBarPosition: positionToString(this.state.sideBar.position), - panelPosition: positionToString(this.state.panel.position), + activityBarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN), + sideBarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN), + auxiliaryBarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN), + panelVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN), + statusbarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN), + sideBarPosition: positionToString(this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON)), + panelPosition: positionToString(this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION)), }; this.telemetryService.publicLog2('startupLayout', layoutDescriptor); @@ -2213,3 +2049,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.disposed = true; } } + +type ZenModeConfiguration = { + centerLayout: boolean; + fullScreen: boolean; + hideActivityBar: boolean; + hideLineNumbers: boolean; + hideStatusBar: boolean; + hideTabs: boolean; + restore: boolean; + silentNotifications: boolean; +}; + +function getZenModeConfiguration(configurationService: IConfigurationService): ZenModeConfiguration { + return configurationService.getValue(WorkbenchLayoutSettings.ZEN_MODE_CONFIG); +} diff --git a/src/vs/workbench/browser/layoutState.ts b/src/vs/workbench/browser/layoutState.ts new file mode 100644 index 0000000000000..975c02a830935 --- /dev/null +++ b/src/vs/workbench/browser/layoutState.ts @@ -0,0 +1,274 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getClientArea } from 'vs/base/browser/dom'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { PanelAlignment, Position, positionFromString, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; + +interface IWorkbenchLayoutStateKey { + name: string, + runtime: boolean, + defaultValue: any, + scope: StorageScope, + target: StorageTarget + zenModeIgnore?: boolean, +} + +type StorageKeyType = string | boolean | number | object; +abstract class WorkbenchLayoutStateKey implements IWorkbenchLayoutStateKey { + abstract readonly runtime: boolean; + constructor(readonly name: string, readonly scope: StorageScope, readonly target: StorageTarget, readonly defaultValue: T) { } +} + +class RuntimeStateKey extends WorkbenchLayoutStateKey { + readonly runtime = true; + constructor(name: string, scope: StorageScope, target: StorageTarget, defaultValue: T, readonly zenModeIgnore?: boolean) { + super(name, scope, target, defaultValue); + } +} + +class InitializationStateKey extends WorkbenchLayoutStateKey { + readonly runtime = false; +} + +export const LayoutStateKeys = { + // Editor + EDITOR_CENTERED: new RuntimeStateKey('editor.centered', StorageScope.WORKSPACE, StorageTarget.USER, false), + + // Zen Mode + ZEN_MODE_ACTIVE: new RuntimeStateKey('zenMode.active', StorageScope.WORKSPACE, StorageTarget.USER, false), + ZEN_MODE_EXIT_INFO: new RuntimeStateKey('zenMode.exitInfo', StorageScope.WORKSPACE, StorageTarget.USER, { + transitionedToCenteredEditorLayout: false, + transitionedToFullScreen: false, + wasVisible: { + auxiliaryBar: false, + panel: false, + sideBar: false, + }, + }), + + // Part Sizing + GRID_SIZE: new InitializationStateKey('grid.size', StorageScope.GLOBAL, StorageTarget.MACHINE, { width: 800, height: 600 }), + SIDEBAR_SIZE: new InitializationStateKey('sideBar.size', StorageScope.GLOBAL, StorageTarget.MACHINE, 200), + AUXILIARYBAR_SIZE: new InitializationStateKey('auxiliaryBar.size', StorageScope.GLOBAL, StorageTarget.MACHINE, 200), + PANEL_SIZE: new InitializationStateKey('panel.size', StorageScope.GLOBAL, StorageTarget.MACHINE, 300), + + PANEL_LAST_NON_MAXIMIZED_HEIGHT: new RuntimeStateKey('panel.lastNonMaximizedHeight', StorageScope.GLOBAL, StorageTarget.MACHINE, 300), + PANEL_LAST_NON_MAXIMIZED_WIDTH: new RuntimeStateKey('panel.lastNonMaximizedWidth', StorageScope.GLOBAL, StorageTarget.MACHINE, 300), + PANEL_WAS_LAST_MAXIMIZED: new RuntimeStateKey('panel.wasLastMaximized', StorageScope.WORKSPACE, StorageTarget.USER, false), + + // Part Positions + SIDEBAR_POSITON: new RuntimeStateKey('sideBar.position', StorageScope.GLOBAL, StorageTarget.USER, Position.LEFT), + PANEL_POSITION: new RuntimeStateKey('panel.position', StorageScope.WORKSPACE, StorageTarget.USER, Position.BOTTOM), + PANEL_ALIGNMENT: new RuntimeStateKey('panel.alignment', StorageScope.GLOBAL, StorageTarget.USER, 'center'), + + // Part Visibility + ACTIVITYBAR_HIDDEN: new RuntimeStateKey('activityBar.hidden', StorageScope.GLOBAL, StorageTarget.USER, false, true), + SIDEBAR_HIDDEN: new RuntimeStateKey('sideBar.hidden', StorageScope.WORKSPACE, StorageTarget.USER, false), + EDITOR_HIDDEN: new RuntimeStateKey('editor.hidden', StorageScope.WORKSPACE, StorageTarget.USER, false), + PANEL_HIDDEN: new RuntimeStateKey('panel.hidden', StorageScope.WORKSPACE, StorageTarget.USER, true), + AUXILIARYBAR_HIDDEN: new RuntimeStateKey('auxiliaryBar.hidden', StorageScope.WORKSPACE, StorageTarget.USER, true), + STATUSBAR_HIDDEN: new RuntimeStateKey('statusBar.hidden', StorageScope.GLOBAL, StorageTarget.USER, false, true), +} as const; + + +interface ILayoutStateChangeEvent { + key: RuntimeStateKey; + value: T; +} +export class LayoutStateModel extends Disposable { + static readonly STORAGE_PREFIX = 'workbench.state.'; + private stateCache = new Map(); + + private readonly _onDidChangeState: Emitter> = this._register(new Emitter>()); + readonly onDidChangeState: Event> = this._onDidChangeState.event; + + constructor( + private readonly storageService: IStorageService, + private readonly configurationService: IConfigurationService, + private readonly container: HTMLElement) { + super(); + this.configurationService.onDidChangeConfiguration(configurationChange => this.updateStateFromLegacySettings(configurationChange)); + this.storageService.onWillSaveState(willSaveState => { + if (willSaveState.reason === WillSaveStateReason.SHUTDOWN) { + this.save(true, true); + } + }); + } + + private updateStateFromLegacySettings(configurationChangeEvent: IConfigurationChangeEvent): void { + const isZenMode = this.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE); + + if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE) && !isZenMode) { + this.setRuntimeValueAndFire(LayoutStateKeys.ACTIVITYBAR_HIDDEN, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE)); + } + + if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE) && !isZenMode) { + this.setRuntimeValueAndFire(LayoutStateKeys.STATUSBAR_HIDDEN, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE)); + } + + if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.PANEL_ALIGNMENT)) { + this.setRuntimeValueAndFire(LayoutStateKeys.PANEL_ALIGNMENT, this.configurationService.getValue(LegacyWorkbenchLayoutSettings.PANEL_ALIGNMENT)); + } + + if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION)) { + this.setRuntimeValueAndFire(LayoutStateKeys.SIDEBAR_POSITON, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left')); + } + } + + private updateLegacySettingsFromState(key: RuntimeStateKey, value: T): void { + const isZenMode = this.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE); + if (key.zenModeIgnore && isZenMode) { + return; + } + + if (key === LayoutStateKeys.ACTIVITYBAR_HIDDEN) { + this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE, !value); + } else if (key === LayoutStateKeys.STATUSBAR_HIDDEN) { + this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, !value); + } else if (key === LayoutStateKeys.PANEL_ALIGNMENT) { + this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.PANEL_ALIGNMENT, value); + } else if (key === LayoutStateKeys.SIDEBAR_POSITON) { + this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, positionToString(value as Position)); + } + } + + load(): void { + let key: keyof typeof LayoutStateKeys; + + // Load stored values for all keys + for (key in LayoutStateKeys) { + const stateKey = LayoutStateKeys[key] as WorkbenchLayoutStateKey; + const value = this.loadKeyFromStorage(stateKey); + + if (value !== undefined) { + this.stateCache.set(stateKey.name, value); + } + } + + // Apply sizing defaults + const workbenchDimensions = getClientArea(this.container); + const panelPosition = this.stateCache.get(LayoutStateKeys.PANEL_POSITION.name) ?? LayoutStateKeys.PANEL_POSITION.defaultValue; + const applySizingIfUndefined = (key: WorkbenchLayoutStateKey, value: T) => { + if (this.stateCache.get(key.name) === undefined) { + this.stateCache.set(key.name, value); + } + }; + + applySizingIfUndefined(LayoutStateKeys.GRID_SIZE, { height: workbenchDimensions.height, width: workbenchDimensions.width }); + applySizingIfUndefined(LayoutStateKeys.SIDEBAR_SIZE, Math.min(300, workbenchDimensions.width / 4)); + applySizingIfUndefined(LayoutStateKeys.AUXILIARYBAR_SIZE, Math.min(300, workbenchDimensions.width / 4)); + applySizingIfUndefined(LayoutStateKeys.PANEL_SIZE, panelPosition === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4); + + // Apply legacy settings + this.stateCache.set(LayoutStateKeys.ACTIVITYBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE)); + this.stateCache.set(LayoutStateKeys.STATUSBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE)); + this.stateCache.set(LayoutStateKeys.PANEL_ALIGNMENT.name, this.configurationService.getValue(LegacyWorkbenchLayoutSettings.PANEL_ALIGNMENT)); + this.stateCache.set(LayoutStateKeys.SIDEBAR_POSITON.name, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left')); + + // Apply all defaults + for (key in LayoutStateKeys) { + const stateKey = LayoutStateKeys[key]; + if (this.stateCache.get(stateKey.name) === undefined) { + this.stateCache.set(stateKey.name, stateKey.defaultValue); + } + } + } + + save(workspace: boolean, global: boolean): void { + let key: keyof typeof LayoutStateKeys; + + const isZenMode = this.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE); + + for (key in LayoutStateKeys) { + const stateKey = LayoutStateKeys[key] as WorkbenchLayoutStateKey; + if ((workspace && stateKey.scope === StorageScope.WORKSPACE) || + (global && stateKey.scope === StorageScope.GLOBAL)) { + // Don't write out specific keys while in zen mode + if (isZenMode && stateKey instanceof RuntimeStateKey && stateKey.zenModeIgnore) { + continue; + } + + this.saveKeyToStorage(stateKey); + } + } + } + + getInitializationValue(key: InitializationStateKey): T { + return this.stateCache.get(key.name) as T; + } + + setInitializationValue(key: InitializationStateKey, value: T): void { + this.stateCache.set(key.name, value); + } + + getRuntimeValue(key: RuntimeStateKey, readFromDisk?: boolean): T { + if (readFromDisk) { + const fromDiskValue = this.loadKeyFromStorage(key); + this.stateCache.set(key.name, fromDiskValue ?? key.defaultValue); + } + + return this.stateCache.get(key.name) as T; + } + + setRuntimeValue(key: RuntimeStateKey, value: T): void { + this.stateCache.set(key.name, value); + const isZenMode = this.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE); + + if (key.scope === StorageScope.GLOBAL) { + if (!isZenMode || !key.zenModeIgnore) { + this.saveKeyToStorage(key); + this.updateLegacySettingsFromState(key, value); + } + } + } + + private setRuntimeValueAndFire(key: RuntimeStateKey, value: T): void { + const previousValue = this.stateCache.get(key.name); + if (previousValue === value) { + return; + } + + this.setRuntimeValue(key, value); + this._onDidChangeState.fire({ key, value }); + } + + private saveKeyToStorage(key: WorkbenchLayoutStateKey): void { + const value = this.stateCache.get(key.name) as T; + this.storageService.store(`${LayoutStateModel.STORAGE_PREFIX}${key.name}`, typeof value === 'object' ? JSON.stringify(value) : value, key.scope, key.target); + } + + private loadKeyFromStorage(key: WorkbenchLayoutStateKey): T | undefined { + let value: any = this.storageService.get(`${LayoutStateModel.STORAGE_PREFIX}${key.name}`, key.scope); + + if (value !== undefined) { + switch (typeof key.defaultValue) { + case 'boolean': value = value === 'true'; break; + case 'number': value = parseInt(value); break; + case 'object': value = JSON.parse(value); break; + } + } + + return value as T | undefined; + } +} + +export enum WorkbenchLayoutSettings { + PANEL_OPENS_MAXIMIZED = 'workbench.panel.opensMaximized', + ZEN_MODE_CONFIG = 'zenMode', + ZEN_MODE_SILENT_NOTIFICATIONS = 'zenMode.silentNotifications', + EDITOR_CENTERED_LAYOUT_AUTO_RESIZE = 'workbench.editor.centeredLayoutAutoResize', +} + +enum LegacyWorkbenchLayoutSettings { + PANEL_POSITION = 'workbench.panel.defaultLocation', // Deprecated to UI State + ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', // Deprecated to UI State + STATUSBAR_VISIBLE = 'workbench.statusBar.visible', // Deprecated to UI State + SIDEBAR_POSITION = 'workbench.sideBar.location', // Deprecated to UI State + PANEL_ALIGNMENT = 'workbench.experimental.panel.alignment', // Deprecated to UI State +} diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 1f422fefbd08b..7bef414841657 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -339,11 +339,11 @@ export class Workbench extends Layout { [ { id: Parts.TITLEBAR_PART, role: 'contentinfo', classes: ['titlebar'] }, { id: Parts.BANNER_PART, role: 'banner', classes: ['banner'] }, - { id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892 - { id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, - { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } }, - { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.state.panel.position)] }, - { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.state.sideBar.position === Position.LEFT ? 'right' : 'left'] }, + { id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892 + { id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, + { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.willRestoreEditors() } }, + { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] }, + { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.getSideBarPosition() === Position.LEFT ? 'right' : 'left'] }, { id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] } ].forEach(({ id, role, classes, options }) => { const partContainer = this.createPart(id, role, classes); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 5d9a5c8ba5888..619b761efa3fc 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -5,7 +5,6 @@ import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { Dimension } from 'vs/base/browser/dom'; @@ -36,6 +35,8 @@ export const enum PanelOpensMaximizedOptions { REMEMBER_LAST } +export type PanelAlignment = 'left' | 'center' | 'right' | 'justified'; + export function positionToString(position: Position): string { switch (position) { case Position.LEFT: return 'left'; @@ -195,11 +196,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ getSideBarPosition(): Position; - /** - * Gets the current menubar visibility. - */ - getMenubarVisibility(): MenuBarVisibility; - /** * Toggles the menu bar visibility. */ From a3482c39bc57ebab1318db3708a777753f956248 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Tue, 4 Jan 2022 11:45:36 -0800 Subject: [PATCH 0985/2210] =?UTF-8?q?=F0=9F=92=84=20Side=20panel=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vs/workbench/browser/parts/panel/media/basepanelpart.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/panel/media/basepanelpart.css b/src/vs/workbench/browser/parts/panel/media/basepanelpart.css index de1a616743119..3d6e0f2561c85 100644 --- a/src/vs/workbench/browser/parts/panel/media/basepanelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/basepanelpart.css @@ -50,7 +50,8 @@ } .monaco-workbench .part.basepanel .empty-panel-message-area .empty-panel-message { - margin: 10px; + margin: 12px; + text-align: center; } .monaco-workbench .part.basepanel > .composite.title> .panel-switcher-container > .monaco-action-bar { From bb7bcbaca8557d965f9697d65833024ffb2f13f7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 17 Dec 2021 15:00:03 +0100 Subject: [PATCH 0986/2210] support rename in indexedDB fsp --- .../browser/indexedDBFileSystemProvider.ts | 130 +++++++--- .../test/browser/indexedDBFileService.test.ts | 230 +++++++++++++++++- 2 files changed, 320 insertions(+), 40 deletions(-) diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts index 55aff88346b63..466113c93bfc8 100644 --- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts +++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts @@ -8,11 +8,11 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { getErrorMessage } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { joinPath } from 'vs/base/common/resources'; +import { ExtUri } from 'vs/base/common/resources'; import { isString } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { createFileSystemProviderError, FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileChange, IStat, IWatchOptions } from 'vs/platform/files/common/files'; +import { createFileSystemProviderError, FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files'; import { IndexedDB } from 'vs/base/browser/indexedDB'; // Standard FS Errors (expected to be thrown in production when invalid FS operations are requested) @@ -61,7 +61,7 @@ class IndexedDBFileSystemNode { return next.doRead(pathParts.slice(1)); } - delete(path: string) { + delete(path: string): void { const toDelete = path.split('/').filter(p => p.length); if (toDelete.length === 0) { if (this.entry.type !== FileType.Directory) { @@ -73,7 +73,7 @@ class IndexedDBFileSystemNode { } } - private doDelete = (pathParts: string[], originalPath: string) => { + private doDelete(pathParts: string[], originalPath: string): void { if (pathParts.length === 0) { throw ERR_UNKNOWN_INTERNAL(`Internal error deleting from IndexedDBFSNode -- got no deletion path parts (encountered while deleting ${originalPath})`); } @@ -90,7 +90,7 @@ class IndexedDBFileSystemNode { } next.doDelete(pathParts.slice(1), originalPath); } - }; + } add(path: string, entry: { type: 'file', size?: number } | { type: 'dir' }) { this.doAdd(path.split('/').filter(p => p.length), entry, path); @@ -224,13 +224,15 @@ class IndexedDBChangesBroadcastChannel extends Disposable { } -export class IndexedDBFileSystemProvider extends Disposable { +export class IndexedDBFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive; readonly onDidChangeCapabilities: Event = Event.None; + private readonly extUri = new ExtUri(() => false) /* Case Sensitive */; + private readonly changesBroadcastChannel: IndexedDBChangesBroadcastChannel | undefined; private readonly _onDidChangeFile = this._register(new Emitter()); readonly onDidChangeFile: Event = this._onDidChangeFile.event; @@ -265,15 +267,18 @@ export class IndexedDBFileSystemProvider extends Disposable { } async stat(resource: URI): Promise { - const content = (await this.getFiletree()).read(resource.path); - if (content?.type === FileType.File) { + const entry = (await this.getFiletree()).read(resource.path); + + if (entry?.type === FileType.File) { return { type: FileType.File, ctime: 0, mtime: this.versions.get(resource.toString()) || 0, - size: content.size ?? (await this.readFile(resource)).byteLength + size: entry.size ?? (await this.readFile(resource)).byteLength }; - } else if (content?.type === FileType.Directory) { + } + + if (entry?.type === FileType.Directory) { return { type: FileType.Directory, ctime: 0, @@ -281,9 +286,8 @@ export class IndexedDBFileSystemProvider extends Disposable { size: 0 }; } - else { - throw ERR_FILE_NOT_FOUND; - } + + throw ERR_FILE_NOT_FOUND; } async readdir(resource: URI): Promise { @@ -326,12 +330,54 @@ export class IndexedDBFileSystemProvider extends Disposable { if (existing?.type === FileType.Directory) { throw ERR_FILE_IS_DIR; } + await this.bulkWrite([[resource, content]]); + } - this.fileWriteBatch.push({ content, resource }); - await this.writeManyThrottler.queue(() => this.writeMany()); - (await this.getFiletree()).add(resource.path, { type: 'file', size: content.byteLength }); - this.versions.set(resource.toString(), (this.versions.get(resource.toString()) || 0) + 1); - this.triggerChanges([{ resource, type: FileChangeType.UPDATED }]); + async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { + const fileTree = await this.getFiletree(); + const fromEntry = fileTree.read(from.path); + if (!fromEntry) { + throw ERR_FILE_NOT_FOUND; + } + + const toEntry = fileTree.read(to.path); + if (toEntry) { + if (!opts.overwrite) { + throw new FileSystemProviderError('file exists already', FileSystemProviderErrorCode.FileExists); + } + if (toEntry.type !== fromEntry.type) { + throw new FileSystemProviderError('Cannot rename files with different types', FileSystemProviderErrorCode.Unknown); + } + // delete the target file if exists + await this.delete(to, { recursive: true, useTrash: false }); + } + + const toTargetResource = (path: string): URI => this.extUri.joinPath(to, this.extUri.relativePath(from, from.with({ path })) || ''); + + const sourceEntries = await this.tree(from); + const sourceFiles: DirEntry[] = []; + for (const sourceEntry of sourceEntries) { + if (sourceEntry[1] === FileType.File) { + sourceFiles.push(sourceEntry); + } else if (sourceEntry[1] === FileType.Directory) { + // add directories to the tree + fileTree.add(toTargetResource(sourceEntry[0]).path, { type: 'dir' }); + } + } + + if (sourceFiles.length) { + const targetFiles: [URI, Uint8Array][] = []; + const sourceFilesContents = await this.indexedDB.runInTransaction(this.store, 'readonly', objectStore => sourceFiles.map(([path]) => objectStore.get(path))); + for (let index = 0; index < sourceFiles.length; index++) { + const content = sourceFilesContents[index] instanceof Uint8Array ? sourceFilesContents[index] : isString(sourceFilesContents[index]) ? VSBuffer.fromString(sourceFilesContents[index]).buffer : undefined; + if (content) { + targetFiles.push([toTargetResource(sourceFiles[index][0]), content]); + } + } + await this.bulkWrite(targetFiles); + } + + await this.delete(from, { recursive: true, useTrash: false }); } async delete(resource: URI, opts: FileDeleteOptions): Promise { @@ -347,7 +393,7 @@ export class IndexedDBFileSystemProvider extends Disposable { let toDelete: string[]; if (opts.recursive) { - const tree = (await this.tree(resource)); + const tree = await this.tree(resource); toDelete = tree.map(([path]) => path); } else { if (stat.type === FileType.Directory && (await this.readdir(resource)).length) { @@ -362,27 +408,20 @@ export class IndexedDBFileSystemProvider extends Disposable { } private async tree(resource: URI): Promise { - if ((await this.stat(resource)).type === FileType.Directory) { - const topLevelEntries = (await this.readdir(resource)).map(([key, type]) => { - return [joinPath(resource, key).path, type] as [string, FileType]; - }); - let allEntries = topLevelEntries; - await Promise.all(topLevelEntries.map( - async ([key, type]) => { - if (type === FileType.Directory) { - const childEntries = (await this.tree(resource.with({ path: key }))); - allEntries = allEntries.concat(childEntries); - } - })); - return allEntries; - } else { - const entries: DirEntry[] = [[resource.path, FileType.File]]; - return entries; + const stat = await this.stat(resource); + const allEntries: DirEntry[] = [[resource.path, stat.type]]; + if (stat.type === FileType.Directory) { + const dirEntries = await this.readdir(resource); + for (const [key, type] of dirEntries) { + const childResource = this.extUri.joinPath(resource, key); + allEntries.push([childResource.path, type]); + if (type === FileType.Directory) { + const childEntries = await this.tree(childResource); + allEntries.push(...childEntries); + } + } } - } - - rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { - return Promise.reject(new Error('Not Supported')); + return allEntries; } private triggerChanges(changes: IFileChange[]): void { @@ -412,6 +451,19 @@ export class IndexedDBFileSystemProvider extends Disposable { return this.cachedFiletree; } + private async bulkWrite(files: [URI, Uint8Array][]): Promise { + files.forEach(([resource, content]) => this.fileWriteBatch.push({ content, resource })); + await this.writeManyThrottler.queue(() => this.writeMany()); + + const fileTree = await this.getFiletree(); + for (const [resource, content] of files) { + fileTree.add(resource.path, { type: 'file', size: content.byteLength }); + this.versions.set(resource.toString(), (this.versions.get(resource.toString()) || 0) + 1); + } + + this.triggerChanges(files.map(([resource]) => ({ resource, type: FileChangeType.UPDATED }))); + } + private fileWriteBatch: { resource: URI, content: Uint8Array }[] = []; private async writeMany() { if (this.fileWriteBatch.length) { diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts index b5a8635edec3f..bf694297ec415 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts @@ -12,7 +12,7 @@ import { basename, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { flakySuite } from 'vs/base/test/common/testUtils'; import { IndexedDBFileSystemProvider } from 'vs/platform/files/browser/indexedDBFileSystemProvider'; -import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderErrorCode, FileType, IFileStatWithMetadata } from 'vs/platform/files/common/files'; +import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -290,6 +290,193 @@ flakySuite('IndexedDBFileSystemProvider', function () { await (Promise.all([single1.assertContentsEmpty(), single2.assertContentsEmpty(), batch1.assertContentsEmpty(), batch2.assertContentsEmpty()])); }); + test('rename not existing file', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFile = joinPath(parent.resource, 'sourceFile'); + const targetFile = joinPath(parent.resource, 'targetFile'); + + try { + await service.move(sourceFile, targetFile, false); + } catch (error) { + assert.deepStrictEqual((error).code, FileSystemProviderErrorCode.FileNotFound); + return; + } + + assert.fail('This should fail with error'); + }); + + test('rename to an existing file without overwrite', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFile = joinPath(parent.resource, 'sourceFile'); + await service.writeFile(sourceFile, VSBuffer.fromString('This is source file')); + + const targetFile = joinPath(parent.resource, 'targetFile'); + await service.writeFile(targetFile, VSBuffer.fromString('This is target file')); + + try { + await service.move(sourceFile, targetFile, false); + } catch (error) { + assert.deepStrictEqual((error).fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT); + return; + } + + assert.fail('This should fail with error'); + }); + + test('rename folder to an existing folder without overwrite', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFolder = joinPath(parent.resource, 'sourceFolder'); + await service.createFolder(sourceFolder); + const targetFolder = joinPath(parent.resource, 'targetFolder'); + await service.createFolder(targetFolder); + + try { + await service.move(sourceFolder, targetFolder, false); + } catch (error) { + assert.deepStrictEqual((error).fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT); + return; + } + + assert.fail('This should fail with cannot overwrite error'); + }); + + test('rename file to a folder', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFile = joinPath(parent.resource, 'sourceFile'); + await service.writeFile(sourceFile, VSBuffer.fromString('This is source file')); + + const targetFolder = joinPath(parent.resource, 'targetFolder'); + await service.createFolder(targetFolder); + + try { + await service.move(sourceFile, targetFolder, false); + } catch (error) { + assert.deepStrictEqual((error).fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT); + return; + } + + assert.fail('This should fail with error'); + }); + + test('rename folder to a file', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFolder = joinPath(parent.resource, 'sourceFile'); + await service.createFolder(sourceFolder); + + const targetFile = joinPath(parent.resource, 'targetFile'); + await service.writeFile(targetFile, VSBuffer.fromString('This is target file')); + + try { + await service.move(sourceFolder, targetFile, false); + } catch (error) { + assert.deepStrictEqual((error).fileOperationResult, FileOperationResult.FILE_MOVE_CONFLICT); + return; + } + + assert.fail('This should fail with error'); + }); + + test('rename file', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFile = joinPath(parent.resource, 'sourceFile'); + await service.writeFile(sourceFile, VSBuffer.fromString('This is source file')); + + const targetFile = joinPath(parent.resource, 'targetFile'); + await service.move(sourceFile, targetFile, false); + + const content = await service.readFile(targetFile); + assert.strictEqual(await service.exists(sourceFile), false); + assert.strictEqual(content.value.toString(), 'This is source file'); + }); + + test('rename to an existing file with overwrite', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFile = joinPath(parent.resource, 'sourceFile'); + await service.writeFile(sourceFile, VSBuffer.fromString('This is source file')); + + const targetFile = joinPath(parent.resource, 'targetFile'); + await service.writeFile(targetFile, VSBuffer.fromString('This is target file')); + + await service.move(sourceFile, targetFile, true); + + const content = await service.readFile(targetFile); + assert.strictEqual(await service.exists(sourceFile), false); + assert.strictEqual(content.value.toString(), 'This is source file'); + }); + + test('rename folder to a new folder', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFolder = joinPath(parent.resource, 'sourceFolder'); + await service.createFolder(sourceFolder); + + const targetFolder = joinPath(parent.resource, 'targetFolder'); + await service.move(sourceFolder, targetFolder, false); + + assert.deepStrictEqual(await service.exists(sourceFolder), false); + assert.deepStrictEqual(await service.exists(targetFolder), true); + }); + + test('rename folder to an existing folder', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const sourceFolder = joinPath(parent.resource, 'sourceFolder'); + await service.createFolder(sourceFolder); + const targetFolder = joinPath(parent.resource, 'targetFolder'); + await service.createFolder(targetFolder); + + await service.move(sourceFolder, targetFolder, true); + + assert.deepStrictEqual(await service.exists(sourceFolder), false); + assert.deepStrictEqual(await service.exists(targetFolder), true); + }); + + test('rename a folder that has multiple files and folders', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + + const sourceFolder = joinPath(parent.resource, 'sourceFolder'); + const sourceFile1 = joinPath(sourceFolder, 'folder1', 'file1'); + await service.writeFile(sourceFile1, VSBuffer.fromString('Source File 1')); + const sourceFile2 = joinPath(sourceFolder, 'folder2', 'file1'); + await service.writeFile(sourceFile2, VSBuffer.fromString('Source File 2')); + const sourceEmptyFolder = joinPath(sourceFolder, 'folder3'); + await service.createFolder(sourceEmptyFolder); + + const targetFolder = joinPath(parent.resource, 'targetFolder'); + const targetFile1 = joinPath(targetFolder, 'folder1', 'file1'); + const targetFile2 = joinPath(targetFolder, 'folder2', 'file1'); + const targetEmptyFolder = joinPath(targetFolder, 'folder3'); + + await service.move(sourceFolder, targetFolder, false); + + assert.deepStrictEqual(await service.exists(sourceFolder), false); + assert.deepStrictEqual(await service.exists(targetFolder), true); + assert.strictEqual((await service.readFile(targetFile1)).value.toString(), 'Source File 1'); + assert.strictEqual((await service.readFile(targetFile2)).value.toString(), 'Source File 2'); + assert.deepStrictEqual(await service.exists(targetEmptyFolder), true); + }); + + test('rename a folder to another folder that has some files', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + + const sourceFolder = joinPath(parent.resource, 'sourceFolder'); + const sourceFile1 = joinPath(sourceFolder, 'folder1', 'file1'); + await service.writeFile(sourceFile1, VSBuffer.fromString('Source File 1')); + + const targetFolder = joinPath(parent.resource, 'targetFolder'); + const targetFile1 = joinPath(targetFolder, 'folder1', 'file1'); + const targetFile2 = joinPath(targetFolder, 'folder1', 'file2'); + await service.writeFile(targetFile2, VSBuffer.fromString('Target File 2')); + const targetFile3 = joinPath(targetFolder, 'folder2', 'file1'); + await service.writeFile(targetFile3, VSBuffer.fromString('Target File 3')); + + await service.move(sourceFolder, targetFolder, true); + + assert.deepStrictEqual(await service.exists(sourceFolder), false); + assert.deepStrictEqual(await service.exists(targetFolder), true); + assert.strictEqual((await service.readFile(targetFile1)).value.toString(), 'Source File 1'); + assert.strictEqual(await service.exists(targetFile2), false); + assert.strictEqual(await service.exists(targetFile3), false); + }); + test('deleteFile', async () => { await initFixtures(); @@ -374,4 +561,45 @@ flakySuite('IndexedDBFileSystemProvider', function () { } assert.ok(error); }); + + test('delete empty folder', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const folder = joinPath(parent.resource, 'folder'); + await service.createFolder(folder); + + await service.del(folder); + + assert.deepStrictEqual(await service.exists(folder), false); + }); + + test('delete empty folder with reccursive', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + const folder = joinPath(parent.resource, 'folder'); + await service.createFolder(folder); + + await service.del(folder, { recursive: true }); + + assert.deepStrictEqual(await service.exists(folder), false); + }); + + test('deleteFolder with folders and files (recursive)', async () => { + const parent = await service.resolve(userdataURIFromPaths([])); + + const targetFolder = joinPath(parent.resource, 'targetFolder'); + const file1 = joinPath(targetFolder, 'folder1', 'file1'); + await service.createFile(file1); + const file2 = joinPath(targetFolder, 'folder2', 'file1'); + await service.createFile(file2); + const emptyFolder = joinPath(targetFolder, 'folder3'); + await service.createFolder(emptyFolder); + + await service.del(targetFolder, { recursive: true }); + + assert.deepStrictEqual(await service.exists(targetFolder), false); + assert.deepStrictEqual(await service.exists(joinPath(targetFolder, 'folder1')), false); + assert.deepStrictEqual(await service.exists(joinPath(targetFolder, 'folder2')), false); + assert.deepStrictEqual(await service.exists(file1), false); + assert.deepStrictEqual(await service.exists(file2), false); + assert.deepStrictEqual(await service.exists(emptyFolder), false); + }); }); From ed86704006ff38f456b27ed6476f1e78d0ea5e89 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 3 Jan 2022 13:04:16 +0100 Subject: [PATCH 0987/2210] apply target filter while querying --- .../common/extensionGalleryService.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index ce0068cd41310..09bb1bc078e1d 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -499,7 +499,6 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi let query = new Query() .withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, identifiers.length) - .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') .withFilter(FilterType.ExtensionName, ...identifiers.map(({ id }) => id.toLowerCase())); if (identifiers.every(identifier => !(identifier).version)) { @@ -525,8 +524,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } let query = new Query() .withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.IncludeLatestVersionOnly) - .withPage(1, identifiers.length) - .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code'); + .withPage(1, identifiers.length); if (ids.length) { query = query.withFilter(FilterType.ExtensionId, ...ids); } @@ -557,8 +555,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const { id, uuid } = extension ? extension.identifier : arg1; let query = new Query() .withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) - .withPage(1, 1) - .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code'); + .withPage(1, 1); if (uuid) { query = query.withFilter(FilterType.ExtensionId, uuid); @@ -638,8 +635,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi let query = new Query() .withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) - .withPage(1, pageSize) - .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code'); + .withPage(1, pageSize); if (text) { // Use category filter instead of "category:themes" @@ -734,7 +730,6 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const query = new Query() .withFlags(Flags.IncludeVersions, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, preReleaseVersions.size) - .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') .withFilter(FilterType.ExtensionId, ...preReleaseVersions.keys()); const { galleryExtensions } = await this.queryGallery(query, targetPlatform, token); this.telemetryService.publicLog2('galleryService:preReleasesQuery', { @@ -760,9 +755,11 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi throw new Error('No extension gallery service configured.'); } - // Always exclude non validated and unpublished extensions query = query + /* Always exclude non validated extensions */ .withFlags(query.flags, Flags.ExcludeNonValidated) + .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') + /* Always exclude unpublished extensions */ .withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished)); const commonHeaders = await this.commonHeadersPromise; @@ -915,8 +912,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi async getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise { let query = new Query() .withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) - .withPage(1, 1) - .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code'); + .withPage(1, 1); if (extension.identifier.uuid) { query = query.withFilter(FilterType.ExtensionId, extension.identifier.uuid); From 030161d0d64c64699335363f0fd05c0d9045bbd3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 3 Jan 2022 16:48:33 +0100 Subject: [PATCH 0988/2210] do not show hover if publisher is not verified --- src/vs/workbench/contrib/extensions/browser/extensionEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index e04b129460743..edcf54328354d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -521,7 +521,7 @@ export class ExtensionEditor extends EditorPane { template.publisher.classList.toggle('clickable', !!extension.url); template.publisherDisplayName.textContent = extension.publisherDisplayName; template.verifiedPublisherIcon.style.display = extension.publisherDomain?.verified ? 'inherit' : 'none'; - template.publisher.title = extension.publisherDomain?.link ? localize('publisher verified tooltip', "This publisher has verified ownership of {0}", URI.parse(extension.publisherDomain.link).authority) : ''; + template.publisher.title = extension.publisherDomain?.verified && extension.publisherDomain.link ? localize('publisher verified tooltip', "This publisher has verified ownership of {0}", URI.parse(extension.publisherDomain.link).authority) : ''; template.installCount.parentElement?.classList.toggle('hide', !extension.url); template.rating.parentElement?.classList.toggle('hide', !extension.url); From 66f67d590410bfff7321eafa42c463c3f6dba578 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 3 Jan 2022 18:53:29 +0100 Subject: [PATCH 0989/2210] Fix #139334 --- .../workbench/contrib/extensions/browser/extensionsActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 20947c126d678..e6320befa6cb6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -773,7 +773,7 @@ export class UpdateAction extends ExtensionAction { private async install(extension: IExtension): Promise { try { - await this.extensionsWorkbenchService.install(extension); + await this.extensionsWorkbenchService.install(extension, extension.local?.preRelease ? { installPreReleaseVersion: true } : undefined); alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); } catch (err) { this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, undefined, err).run(); From de0724b414e2f95f6cc484b03bccbc96686c2cfd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 4 Jan 2022 11:51:41 +0100 Subject: [PATCH 0990/2210] add flags info to query telemetry --- .../extensionManagement/common/extensionGalleryService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 09bb1bc078e1d..5e8e619ea50a8 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -171,6 +171,7 @@ const DefaultQueryState: IQueryState = { type GalleryServiceQueryClassification = { readonly filterTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + readonly flags: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; readonly sortBy: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; readonly sortOrder: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; readonly duration: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', 'isMeasurement': true }; @@ -183,6 +184,7 @@ type GalleryServiceQueryClassification = { }; type QueryTelemetryData = { + readonly flags: number; readonly filterTypes: string[]; readonly sortBy: string; readonly sortOrder: string; @@ -261,6 +263,7 @@ class Query { get telemetryData(): QueryTelemetryData { return { filterTypes: this.state.criteria.map(criterium => String(criterium.filterType)), + flags: this.state.flags, sortBy: String(this.sortBy), sortOrder: String(this.sortOrder) }; From f559de25103d59c3c06b0ac886057d2be4a4cd18 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 4 Jan 2022 21:08:03 +0100 Subject: [PATCH 0991/2210] rename test --- src/vs/platform/files/test/browser/indexedDBFileService.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts index bf694297ec415..ec314be1b6135 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts @@ -290,7 +290,7 @@ flakySuite('IndexedDBFileSystemProvider', function () { await (Promise.all([single1.assertContentsEmpty(), single2.assertContentsEmpty(), batch1.assertContentsEmpty(), batch2.assertContentsEmpty()])); }); - test('rename not existing file', async () => { + test('rename not existing resource', async () => { const parent = await service.resolve(userdataURIFromPaths([])); const sourceFile = joinPath(parent.resource, 'sourceFile'); const targetFile = joinPath(parent.resource, 'targetFile'); From 8e658894e96ad15bd478447fdc825773cd117689 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 4 Jan 2022 22:02:36 +0100 Subject: [PATCH 0992/2210] fix: do not lower the case of extension id migrate storage from previous id to current id --- .../api/browser/mainThreadStorage.ts | 76 +++++++++++++++++-- .../services/extensions/common/extensions.ts | 4 +- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadStorage.ts b/src/vs/workbench/api/browser/mainThreadStorage.ts index 306b07bb4197e..480a68264180c 100644 --- a/src/vs/workbench/api/browser/mainThreadStorage.ts +++ b/src/vs/workbench/api/browser/mainThreadStorage.ts @@ -9,24 +9,30 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IExtensionIdWithVersion, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; import { ILogService } from 'vs/platform/log/common/log'; +import { FileSystemProviderError, FileSystemProviderErrorCode, IFileService } from 'vs/platform/files/common/files'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { isWeb } from 'vs/base/common/platform'; +import { getErrorMessage } from 'vs/base/common/errors'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @extHostNamedCustomer(MainContext.MainThreadStorage) export class MainThreadStorage implements MainThreadStorageShape { - private readonly _storageService: IStorageService; - private readonly _extensionsStorageSyncService: IExtensionsStorageSyncService; private readonly _proxy: ExtHostStorageShape; private readonly _storageListener: IDisposable; private readonly _sharedStorageKeysToWatch: Map = new Map(); constructor( extHostContext: IExtHostContext, - @IStorageService storageService: IStorageService, - @IExtensionsStorageSyncService extensionsStorageSyncService: IExtensionsStorageSyncService, + @IStorageService private readonly _storageService: IStorageService, + @IExtensionsStorageSyncService private readonly _extensionsStorageSyncService: IExtensionsStorageSyncService, + @IFileService private readonly _fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @ILogService private readonly _logService: ILogService ) { - this._storageService = storageService; - this._extensionsStorageSyncService = extensionsStorageSyncService; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostStorage); this._storageListener = this._storageService.onDidChangeValue(e => { @@ -42,6 +48,9 @@ export class MainThreadStorage implements MainThreadStorageShape { } async $getValue(shared: boolean, key: string): Promise { + if (isWeb && key !== key.toLowerCase()) { + await this._migrateExtensionStorage(key.toLowerCase(), key, `extension.storage.migrateFromLowerCaseKey.${key.toLowerCase()}`); + } if (shared) { this._sharedStorageKeysToWatch.set(key, true); } @@ -67,7 +76,62 @@ export class MainThreadStorage implements MainThreadStorageShape { this._storageService.store(key, JSON.stringify(value), shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE, StorageTarget.MACHINE /* Extension state is synced separately through extensions */); } + private _remove(shared: boolean, key: string): void { + this._storageService.remove(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + } + $registerExtensionStorageKeysToSync(extension: IExtensionIdWithVersion, keys: string[]): void { this._extensionsStorageSyncService.setKeysForSync(extension, keys); } + + private async _migrateExtensionStorage(from: string, to: string, storageMigratedKey: string): Promise { + if (from === to) { + return; + } + + const extUri = this.uriIdentityService.extUri; + // Migrate Global Storage + if (!this._storageService.getBoolean(storageMigratedKey, StorageScope.GLOBAL, false)) { + const value = this._getValue(true, from); + if (value) { + this.$setValue(true, to, value); + this._remove(true, from); + } + + const fromPath = extUri.joinPath(this.environmentService.globalStorageHome, from); + const toPath = extUri.joinPath(this.environmentService.globalStorageHome, to.toLowerCase() /* Extension id is lower cased for global storage */); + if (!extUri.isEqual(fromPath, toPath)) { + try { + await this._fileService.move(fromPath, toPath, true); + } catch (error) { + if ((error).code !== FileSystemProviderErrorCode.FileNotFound) { + this._logService.info(`Error while migrating global storage from '${from}' to '${to}'`, getErrorMessage(error)); + } + } + } + + this._storageService.store(storageMigratedKey, true, StorageScope.GLOBAL, StorageTarget.MACHINE); + } + + // Migrate Workspace Storage + if (!this._storageService.getBoolean(storageMigratedKey, StorageScope.WORKSPACE, false)) { + const value = this._getValue(false, from); + if (value) { + this.$setValue(false, to, value); + this._remove(false, from); + } + + const fromPath = extUri.joinPath(this.environmentService.workspaceStorageHome, this.workspaceContextService.getWorkspace().id, from); + const toPath = extUri.joinPath(this.environmentService.workspaceStorageHome, this.workspaceContextService.getWorkspace().id, to); + try { + await this._fileService.move(fromPath, toPath, true); + } catch (error) { + if ((error).code !== FileSystemProviderErrorCode.FileNotFound) { + this._logService.info(`Error while migrating workspace storage from '${from}' to '${to}'`, getErrorMessage(error)); + } + } + + this._storageService.store(storageMigratedKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + } } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 570b26b5fef30..a6b40d865d486 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription, IExtensionContributions } from 'vs/platform/extensions/common/extensions'; -import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ApiProposalName } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; @@ -345,7 +345,7 @@ export function toExtension(extensionDescription: IExtensionDescription): IExten export function toExtensionDescription(extension: IExtension, isUnderDevelopment?: boolean): IExtensionDescription { return { - identifier: new ExtensionIdentifier(extension.identifier.id), + identifier: new ExtensionIdentifier(getExtensionId(extension.manifest.publisher, extension.manifest.name)), isBuiltin: extension.type === ExtensionType.System, isUserBuiltin: extension.type === ExtensionType.User && extension.isBuiltin, isUnderDevelopment: !!isUnderDevelopment, From fa96664a8e492db32e6b5d23c19c14e2516219dc Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 4 Jan 2022 14:10:41 -0800 Subject: [PATCH 0993/2210] Update decoration async. --- .../browser/contrib/find/findController.ts | 34 ++++-- .../browser/contrib/find/findModel.ts | 105 +++++++++++------- .../notebook/browser/notebookBrowser.ts | 1 + .../notebook/browser/notebookEditorWidget.ts | 8 ++ .../view/renderers/backLayerWebView.ts | 7 ++ .../browser/view/renderers/webviewMessages.ts | 6 + .../browser/view/renderers/webviewPreloads.ts | 28 +++++ .../browser/viewModel/markupCellViewModel.ts | 3 +- 8 files changed, 138 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index e6f51385d9561..df2f8c4832c67 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -29,6 +29,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { NLS_MATCHES_LOCATION, NLS_NO_RESULTS } from 'vs/editor/contrib/find/findWidget'; import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { FindMatch } from 'vs/editor/common/model'; const FIND_HIDE_TRANSITION = 'find-hide-transition'; const FIND_SHOW_TRANSITION = 'find-show-transition'; @@ -122,16 +123,24 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this._findModel.find(false); } - const { cell, match } = this._findModel.getCurrentMatch(); - this._progressBar.infinite().show(); + const currentMatch = this._findModel.getCurrentMatch(); + const cell = currentMatch.cell; + if (currentMatch.isModelMatch) { + const match = currentMatch.match as FindMatch; - const replacePattern = this.replacePattern; - const replaceString = replacePattern.buildReplaceString(match.matches, this._state.preserveCase); + this._progressBar.infinite().show(); - const viewModel = this._notebookEditor._getViewModel(); - viewModel.replaceOne(cell, match.range, replaceString).then(() => { - this._progressBar.stop(); - }); + const replacePattern = this.replacePattern; + const replaceString = replacePattern.buildReplaceString(match.matches, this._state.preserveCase); + + const viewModel = this._notebookEditor._getViewModel(); + viewModel.replaceOne(cell, match.range, replaceString).then(() => { + this._progressBar.stop(); + }); + } else { + // this should not work + console.error('Replace does not work for output match'); + } } protected replaceAll() { @@ -147,9 +156,12 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote let replaceStrings: string[] = []; cellFindMatches.forEach(cellFindMatch => { const findMatches = cellFindMatch.matches; - findMatches.forEach(findMatch => { - const matches = findMatch.matches; - replaceStrings.push(replacePattern.buildReplaceString(matches, this._state.preserveCase)); + findMatches.forEach((findMatch, index) => { + if (index < cellFindMatch.modelMatchCount) { + const match = findMatch as FindMatch; + const matches = match.matches; + replaceStrings.push(replacePattern.buildReplaceString(matches, this._state.preserveCase)); + } }); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index c90022a119798..ff9787050f8d0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -18,7 +18,6 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { findFirstInSorted } from 'vs/base/common/arrays'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; export class FindModel extends Disposable { @@ -81,7 +80,8 @@ export class FindModel extends Disposable { return { cell, - match + match, + isModelMatch: nextIndex.remainder < this._findMatches[nextIndex.index].modelMatchCount }; } @@ -126,7 +126,7 @@ export class FindModel extends Disposable { this._notebookEditor.focusElement(findMatch.cell); const index = this._notebookEditor.getCellIndex(findMatch.cell); if (index !== undefined) { - const range: ICellRange = { start: index, end: index + 1 }; + // const range: ICellRange = { start: index, end: index + 1 }; this._notebookEditor.revealCellOffsetInCenterAsync(findMatch.cell, outputOffset ?? 0); } } else { @@ -190,6 +190,11 @@ export class FindModel extends Disposable { const oldCurrCell = this._findMatches[oldCurrIndex.index].cell; const oldCurrMatchCellIndex = this._notebookEditor.getCellIndex(oldCurrCell); + const findFirstMatchAfterCellIndex = (cellIndex: number) => { + const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= cellIndex); + this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); + }; + if (oldCurrMatchCellIndex < 0) { // the cell containing the active match is deleted if (this._notebookEditor.getLength() === 0) { @@ -197,59 +202,68 @@ export class FindModel extends Disposable { return; } - const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= oldCurrMatchCellIndex); - this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); + findFirstMatchAfterCellIndex(oldCurrMatchCellIndex); return; } // the cell still exist const cell = this._notebookEditor.cellAt(oldCurrMatchCellIndex); + // we will try restore the active find match in this cell, if it contains any find match + if (cell.cellKind === CellKind.Markup && cell.getEditState() === CellEditState.Preview) { - // find the nearest match above this cell - const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= oldCurrMatchCellIndex); - this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); + // find first match in this cell or below + findFirstMatchAfterCellIndex(oldCurrMatchCellIndex); + return; + } + + // the cell is a markdown cell in editing mode or a code cell, both should have monaco editor rendered + + if (!this._currentMatchDecorations) { + // no current highlight decoration + findFirstMatchAfterCellIndex(oldCurrMatchCellIndex); return; } - if ((cell.cellKind === CellKind.Markup && cell.getEditState() === CellEditState.Editing) || cell.cellKind === CellKind.Code) { - // check if there is monaco editor selection and find the first match, otherwise find the first match above current cell - // this._findMatches[cellIndex].matches[matchIndex].range - if (this._currentMatchDecorations) { - if (this._currentMatchDecorations.kind === 'input') { - const currentMatchDecorationId = this._currentMatchDecorations.decorations.find(decoration => decoration.ownerId === cell.handle); - - if (currentMatchDecorationId) { - const matchAfterSelection = findFirstInSorted(findMatches, match => match.index >= oldCurrMatchCellIndex) % findMatches.length; - if (findMatches[matchAfterSelection].index > oldCurrMatchCellIndex) { - // there is no search result in curr cell anymore - this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); - } else { - const currMatchRangeInEditor = (cell.editorAttached && currentMatchDecorationId.decorations[0] ? cell.getCellDecorationRange(currentMatchDecorationId.decorations[0]) : null) - ?? this._findMatches[oldCurrIndex.index].matches[oldCurrIndex.remainder].range; - - const cellMatch = findMatches[matchAfterSelection]; - const matchAfterOldSelection = findFirstInSorted(cellMatch.matches, match => Range.compareRangesUsingStarts(match.range, currMatchRangeInEditor) >= 0); - this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection) + matchAfterOldSelection); - } - } else { - // can't find the decoration - const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= oldCurrMatchCellIndex); - this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); - } + // check if there is monaco editor selection and find the first match, otherwise find the first match above current cell + // this._findMatches[cellIndex].matches[matchIndex].range + if (this._currentMatchDecorations.kind === 'input') { + const currentMatchDecorationId = this._currentMatchDecorations.decorations.find(decoration => decoration.ownerId === cell.handle); + + if (!currentMatchDecorationId) { + // current match decoration is no longer valid + findFirstMatchAfterCellIndex(oldCurrMatchCellIndex); + return; + } + + const matchAfterSelection = findFirstInSorted(findMatches, match => match.index >= oldCurrMatchCellIndex) % findMatches.length; + if (findMatches[matchAfterSelection].index > oldCurrMatchCellIndex) { + // there is no search result in curr cell anymore, find the nearest one (from top to bottom) + this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); + return; + } else { + // there are still some search results in current cell + let currMatchRangeInEditor = cell.editorAttached && currentMatchDecorationId.decorations[0] ? cell.getCellDecorationRange(currentMatchDecorationId.decorations[0]) : null; + + if (currMatchRangeInEditor === null && oldCurrIndex.remainder < this._findMatches[oldCurrIndex.index].modelMatchCount) { + currMatchRangeInEditor = (this._findMatches[oldCurrIndex.index].matches[oldCurrIndex.remainder] as FindMatch).range; + } + + if (currMatchRangeInEditor !== null) { + // we find a range for the previous current match, let's find the nearest one after it (can overlap) + const cellMatch = findMatches[matchAfterSelection]; + const matchAfterOldSelection = findFirstInSorted(cellMatch.matches.slice(0, cellMatch.modelMatchCount), match => Range.compareRangesUsingStarts((match as FindMatch).range, currMatchRangeInEditor) >= 0); + this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection) + matchAfterOldSelection); } else { - // output now has the highlight - const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= oldCurrMatchCellIndex); + // no range found, let's fall back to finding the nearest match this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); + return; } - } else { - const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= oldCurrMatchCellIndex); - this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); } - - return; + } else { + // output now has the highlight + const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= oldCurrMatchCellIndex); + this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); } - - this.set(findMatches, false); } private set(cellFindMatches: CellFindMatch[] | null, autoStart: boolean): void { @@ -347,6 +361,8 @@ export class FindModel extends Disposable { const cell = this._findMatches[cellIndex].cell; if (matchIndex < this._findMatches[cellIndex].modelMatchCount) { + this.clearCurrentFindMatchDecoration(); + const match = this._findMatches[cellIndex].matches[matchIndex] as FindMatch; // match is an editor FindMatch, we update find match decoration in the editor // we will highlight the match in the webview @@ -369,8 +385,11 @@ export class FindModel extends Disposable { return null; } else { + this.clearCurrentFindMatchDecoration(); + const match = this._findMatches[cellIndex].matches[matchIndex] as OutputFindMatch; const offset = await this._notebookEditor.highlightFind(cell, match.index); + this._currentMatchDecorations = { kind: 'output', index: match.index }; return offset; } } @@ -381,6 +400,8 @@ export class FindModel extends Disposable { accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], []); this._currentMatchDecorations = null; }); + } else if (this._currentMatchDecorations?.kind === 'output') { + this._notebookEditor.unHighlightFind(this._currentMatchDecorations.index); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 9ed15bbf8f3ac..745a177e33e08 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -636,6 +636,7 @@ export interface INotebookEditor { getNextVisibleCellIndex(index: number): number | undefined; find(query: string, options: INotebookSearchOptions, token: CancellationToken): Promise; highlightFind(cell: ICellViewModel, matchIndex: number): Promise; + unHighlightFind(matchIndex: number): Promise; findStop(): void; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 13faf91248cae..4aae787cdc91a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2314,6 +2314,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._webview?.findHighlight(matchIndex); } + async unHighlightFind(matchIndex: number): Promise { + if (!this._webview) { + return; + } + + return this._webview?.findUnHighlight(matchIndex); + } + findStop() { if (this._webview) { this._webview.findStop(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 214f87518573b..28f1d48f66e7f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -1316,6 +1316,13 @@ var requirejs = (function() { return ret; } + async findUnHighlight(index: number): Promise { + this._sendMessageToWebview({ + type: 'findUnHighlight', + index + }); + } + deltaCellOutputContainerClassNames(cellId: string, added: string[], removed: string[]) { this._sendMessageToWebview({ diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index e55c2cbcec26b..eb1980dbccc74 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -375,6 +375,11 @@ export interface IFindHighlightMessage { readonly index: number; } +export interface IFindUnHighlightMessage { + readonly type: 'findUnHighlight'; + readonly index: number; +} + export interface IFindStopMessage { readonly type: 'findStop'; } @@ -453,6 +458,7 @@ export type ToWebviewMessage = IClearMessage | ITokenizedStylesChangedMessage | IFindMessage | IFindHighlightMessage | + IFindUnHighlightMessage | IFindStopMessage; export type AnyMessage = FromWebviewMessage | ToWebviewMessage; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index d490b389133f7..2ea5097fa6250 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -714,6 +714,30 @@ async function webviewPreloads(ctx: PreloadContext) { } }; + const unHighlightCurrentMatch = (index: number) => { + const oldMatch = _findingMatches[index]; + if (oldMatch) { + const sel = window.getSelection(); + if (sel) { + try { + sel.removeAllRanges(); + const r = document.createRange(); + r.setStart(oldMatch.range.startContainer, oldMatch.range.startOffset); + r.setEnd(oldMatch.range.endContainer, oldMatch.range.endOffset); + sel.addRange(r); + document.designMode = 'On'; + document.execCommand('removeFormat', false, undefined); + window.document.execCommand('hiliteColor', false, matchColor); + document.designMode = 'Off'; + + sel.removeAllRanges(); + } catch (e) { + console.log(e); + } + } + } + }; + const clearFindMatches = () => { _findingMatches.forEach(match => { const sel = window.getSelection(); @@ -910,6 +934,10 @@ async function webviewPreloads(ctx: PreloadContext) { highlightCurrentMatch(event.data.index); break; } + case 'findUnHighlight': { + unHighlightCurrentMatch(event.data.index); + break; + } case 'findStop': { clearFindMatches(); break; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts index 7d2dd6d56dd00..50b9c59248a18 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts @@ -271,7 +271,8 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM return { cell: this, - matches + matches, + modelMatchCount: matches.length }; } From 6ce37ef7a2ad0d093edc5d5ae38d8f3acd9ed8c7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Jan 2022 14:28:16 -0800 Subject: [PATCH 0994/2210] Skip flaky test Part of #140111 --- .../vscode-api-tests/src/singlefolder-tests/terminal.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 5f5463b846bae..8a5d124c8ef2e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -667,7 +667,7 @@ import { assertNoRpc, poll } from '../utils'; }); suite('environmentVariableCollection', () => { - test('should have collection variables apply to terminals immediately after setting', async () => { + test.skip('should have collection variables apply to terminals immediately after setting', async () => { // Setup collection and create terminal const collection = extensionContext.environmentVariableCollection; disposables.push({ dispose: () => collection.clear() }); From 259c20e8d110af32925fb154c47c1d771ef1afa4 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 4 Jan 2022 14:29:31 -0800 Subject: [PATCH 0995/2210] debug: close memory view automatically when debug session ends --- .../contrib/debug/browser/variablesView.ts | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 837a447ae9f10..9eda83a19ba29 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -24,7 +24,7 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -488,19 +488,40 @@ CommandsRegistry.registerCommand({ return; } + const debugService = accessor.get(IDebugService); const commandService = accessor.get(ICommandService); const editorService = accessor.get(IEditorService); const ext = await accessor.get(IExtensionService).getExtension(HEX_EDITOR_EXTENSION_ID); if (!ext) { await commandService.executeCommand('workbench.extensions.search', `@id:${HEX_EDITOR_EXTENSION_ID}`); - } else { - await editorService.openEditor({ - resource: getUriForDebugMemory(arg.sessionId, arg.variable.memoryReference), - options: { - override: HEX_EDITOR_EDITOR_ID, - }, - }); + return; } + + const pane = await editorService.openEditor({ + resource: getUriForDebugMemory(arg.sessionId, arg.variable.memoryReference), + options: { + revealIfOpened: true, + override: HEX_EDITOR_EDITOR_ID, + }, + }); + + const editor = pane?.input; + if (!editor) { + return; + } + + const disposable = new DisposableStore(); + disposable.add(editor); + disposable.add(debugService.onDidEndSession(session => { + if (session.getId() === arg.sessionId) { + disposable.dispose(); + } + })); + disposable.add(editorService.onDidCloseEditor(e => { + if (e.editor === editor) { + disposable.dispose(); + } + })); } }); From c0c135f120f0d36ca066c5882e55ce96ea863424 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 4 Jan 2022 14:40:30 -0800 Subject: [PATCH 0996/2210] update ob pipeline --- .../azure-pipelines/config/CredScanSuppressions.json | 11 +++++++++++ build/azure-pipelines/config/tsaoptions.json | 12 ++++++++++++ build/azure-pipelines/product-onebranch.yml | 2 ++ 3 files changed, 25 insertions(+) create mode 100644 build/azure-pipelines/config/CredScanSuppressions.json create mode 100644 build/azure-pipelines/config/tsaoptions.json diff --git a/build/azure-pipelines/config/CredScanSuppressions.json b/build/azure-pipelines/config/CredScanSuppressions.json new file mode 100644 index 0000000000000..312a5560cbd6e --- /dev/null +++ b/build/azure-pipelines/config/CredScanSuppressions.json @@ -0,0 +1,11 @@ +{ + "tool": "Credential Scanner", + "suppressions": [ + { + "file": [ + "src/vs/base/test/common/uri.test.ts" + ], + "_justification": "These are not passwords, they are URIs." + } + ] +} diff --git a/build/azure-pipelines/config/tsaoptions.json b/build/azure-pipelines/config/tsaoptions.json new file mode 100644 index 0000000000000..560d0c2513ab2 --- /dev/null +++ b/build/azure-pipelines/config/tsaoptions.json @@ -0,0 +1,12 @@ +{ + "instanceUrl": "https://msazure.visualstudio.com/defaultcollection", + "projectName": "One", + "areaPath": "One\\VSCode\\Client", + "iterationPath": "One", + "notificationAliases": [ + "sbatten@microsoft.com" + ], + "ppe": "false", + "template": "TFSMSAzure", + "codebaseName": "vscode-client" +} diff --git a/build/azure-pipelines/product-onebranch.yml b/build/azure-pipelines/product-onebranch.yml index bd99274936f6b..6241e0c0ee49e 100644 --- a/build/azure-pipelines/product-onebranch.yml +++ b/build/azure-pipelines/product-onebranch.yml @@ -28,6 +28,8 @@ extends: globalSdl: policheck: break: true + credscan: + suppressionsFile: $(Build.SourcesDirectory)\build\azure-pipelines\config\CredScanSuppressions.json stages: - stage: Compile From fc76a875557a61702855978dbbe32e60b61dccc4 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 4 Jan 2022 16:19:25 -0800 Subject: [PATCH 0997/2210] Fix #137544 --- .../gettingStarted/browser/gettingStarted.ts | 39 ++++++++++++++++++- .../browser/gettingStartedService.ts | 1 - .../welcome/page/browser/welcomePage.ts | 28 +++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index 43844add86d77..f3c5908c0ab1e 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -53,6 +53,7 @@ import { TokenizationRegistry } from 'vs/editor/common/languages'; import { generateTokensCSSForColorMap } from 'vs/editor/common/languages/supports/tokenization'; import { ResourceMap } from 'vs/base/common/map'; import { IFileService } from 'vs/platform/files/common/files'; +import { parse } from 'vs/base/common/marshalling'; import { joinPath } from 'vs/base/common/resources'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { asWebviewUri } from 'vs/workbench/api/common/shared/webview'; @@ -72,6 +73,7 @@ import { IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; import { AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { Codicon } from 'vs/base/common/codicons'; +import { restoreWalkthroughsConfigurationKey, RestoreWalkthroughsConfigurationValue } from 'vs/workbench/contrib/welcome/page/browser/welcomePage'; const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; @@ -1312,7 +1314,42 @@ export class GettingStartedPage extends EditorPane { nonGettingStartedGroup.focus(); } } - this.openerService.open(command, { allowCommands: true }); + if (isCommand) { + const commandURI = URI.parse(command); + + // execute as command + let args: any = []; + try { + args = parse(decodeURIComponent(commandURI.query)); + } catch { + // ignore and retry + try { + args = parse(commandURI.query); + } catch { + // ignore error + } + } + if (!Array.isArray(args)) { + args = [args]; + } + this.commandService.executeCommand(commandURI.path, ...args).then(result => { + const toOpen: URI = result?.openFolder; + if (toOpen) { + if (!URI.isUri(toOpen)) { + console.warn('Warn: Running walkthrough command', href, 'yielded non-URI `openFolder` result', toOpen, '. It will be disregarded.'); + return; + } + const restoreData: RestoreWalkthroughsConfigurationValue = { folder: toOpen.toString(), category: this.editorInput.selectedCategory, step: this.editorInput.selectedStep }; + this.storageService.store( + restoreWalkthroughsConfigurationKey, + JSON.stringify(restoreData), + StorageScope.GLOBAL, StorageTarget.MACHINE); + this.hostService.openWindow([{ folderUri: toOpen }]); + } + }); + } else { + this.openerService.open(command, { allowCommands: true }); + } if (!isCommand && (href.startsWith('https://') || href.startsWith('http://'))) { this.gettingStartedService.progressByEvent('onLink:' + href); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts index ac121b960c914..96e98c7d2f6ce 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts @@ -28,7 +28,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { dirname } from 'vs/base/common/path'; import { coalesce, flatten } from 'vs/base/common/arrays'; import { IViewsService } from 'vs/workbench/common/views'; - import { localize } from 'vs/nls'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { checkGlobFileExists } from 'vs/workbench/api/common/shared/workspaceContains'; diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index a930df5bbc04c..97049439ddbed 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -25,6 +25,9 @@ import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { IProductService } from 'vs/platform/product/common/productService'; +export const restoreWalkthroughsConfigurationKey = 'workbench.welcomePage.restorableWalkthroughs'; +export type RestoreWalkthroughsConfigurationValue = { folder: string, category?: string, step?: string, }; + const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; const telemetryOptOutStorageKey = 'workbench.telemetryOptOutShown'; @@ -63,6 +66,10 @@ export class WelcomePageContribution implements IWorkbenchContribution { return; } + if (this.tryOpenWalkthroughForFolder()) { + return; + } + const enabled = isWelcomePageEnabled(this.configurationService, this.contextService, this.environmentService); if (enabled && this.lifecycleService.startupKind !== StartupKind.ReloadedWindow) { const hasBackups = await this.workingCopyBackupService.hasBackups(); @@ -86,6 +93,27 @@ export class WelcomePageContribution implements IWorkbenchContribution { } } + private tryOpenWalkthroughForFolder(): boolean { + const toRestore = this.storageService.get(restoreWalkthroughsConfigurationKey, StorageScope.GLOBAL); + if (!toRestore) { + return false; + } + else { + const restoreData: RestoreWalkthroughsConfigurationValue = JSON.parse(toRestore); + const currentWorkspace = this.contextService.getWorkspace(); + if (restoreData.folder === currentWorkspace.folders[0].uri.toString()) { + this.editorService.openEditor( + this.instantiationService.createInstance( + GettingStartedInput, + { selectedCategory: restoreData.category, selectedStep: restoreData.step }), + { pinned: false }); + this.storageService.remove(restoreWalkthroughsConfigurationKey, StorageScope.GLOBAL); + return true; + } + } + return false; + } + private async openReadme() { const readmes = arrays.coalesce( await Promise.all(this.contextService.getWorkspace().folders.map( From 200a679b2308a5149e1f998be696f9a75a5cba66 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 4 Jan 2022 16:15:23 -0800 Subject: [PATCH 0998/2210] debug: support inline actions on variables, show data inspector inline --- .../contrib/debug/browser/baseDebugView.ts | 30 ++- .../debug/browser/debug.contribution.ts | 9 +- .../contrib/debug/browser/debugIcons.ts | 2 + .../debug/browser/media/debugViewlet.css | 17 +- .../contrib/debug/browser/variablesView.ts | 245 +++++++++--------- 5 files changed, 172 insertions(+), 131 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 91e9625030dbc..48a01082eb1d8 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -9,7 +9,7 @@ import { Expression, Variable, ExpressionContainer } from 'vs/workbench/contrib/ import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -19,6 +19,7 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; import { once } from 'vs/base/common/functional'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -131,7 +132,8 @@ export interface IExpressionTemplateData { name: HTMLSpanElement; value: HTMLSpanElement; inputBoxContainer: HTMLElement; - toDispose: IDisposable; + actionBar?: ActionBar; + elementDisposable: IDisposable[]; label: HighlightedLabel; } @@ -153,20 +155,26 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer, index: number, data: IExpressionTemplateData): void { - data.toDispose.dispose(); - data.toDispose = Disposable.None; const { element } = node; this.renderExpression(element, data, createMatches(node.filterData)); + if (data.actionBar) { + this.renderActionBar!(data.actionBar, element, data); + } const selectedExpression = this.debugService.getViewModel().getSelectedExpression(); if (element === selectedExpression?.expression || (element instanceof Variable && element.errorMessage)) { const options = this.getInputBoxOptions(element, !!selectedExpression?.settingWatch); if (options) { - data.toDispose = this.renderInputBox(data.name, data.value, data.inputBoxContainer, options); - return; + data.elementDisposable.push(this.renderInputBox(data.name, data.value, data.inputBoxContainer, options)); } } } @@ -226,11 +234,15 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer, index: number, templateData: IExpressionTemplateData): void { - templateData.toDispose.dispose(); + dispose(templateData.elementDisposable); + templateData.elementDisposable = []; } disposeTemplate(templateData: IExpressionTemplateData): void { - templateData.toDispose.dispose(); + dispose(templateData.elementDisposable); + templateData.actionBar?.dispose(); } } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 367a3633517f6..c779d9521595a 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/debug.contribution'; import 'vs/css!./media/debugHover'; import * as nls from 'vs/nls'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, Icon } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -118,14 +118,16 @@ registerDebugCommandPaletteItem(SELECT_AND_START_ID, SELECT_AND_START_LABEL, Con // Debug callstack context menu -const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation') => { +const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => { MenuRegistry.appendMenuItem(menuId, { group, when, order, + icon, command: { id, title, + icon, precondition } }); @@ -142,8 +144,9 @@ registerDebugViewMenuItem(MenuId.DebugCallStackContext, TERMINATE_THREAD_ID, nls registerDebugViewMenuItem(MenuId.DebugCallStackContext, RESTART_FRAME_ID, nls.localize('restartFrame', "Restart Frame"), 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), CONTEXT_RESTART_FRAME_SUPPORTED), CONTEXT_STACK_FRAME_SUPPORTS_RESTART); registerDebugViewMenuItem(MenuId.DebugCallStackContext, COPY_STACK_TRACE_ID, nls.localize('copyStackTrace', "Copy Call Stack"), 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), undefined, '3_modification'); +registerDebugViewMenuItem(MenuId.DebugVariablesContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Memory"), 15, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_IN_DEBUG_MODE, 'inline', icons.debugInspectMemory); + registerDebugViewMenuItem(MenuId.DebugVariablesContext, SET_VARIABLE_ID, nls.localize('setValue', "Set Value"), 10, ContextKeyExpr.or(CONTEXT_SET_VARIABLE_SUPPORTED, ContextKeyExpr.and(CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_SET_EXPRESSION_SUPPORTED)), CONTEXT_VARIABLE_IS_READONLY.toNegated(), '3_modification'); -registerDebugViewMenuItem(MenuId.DebugVariablesContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Memory"), 15, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_IN_DEBUG_MODE, '3_modification'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, COPY_VALUE_ID, nls.localize('copyValue', "Copy Value"), 10, undefined, undefined, '5_cutcopypaste'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, COPY_EVALUATE_PATH_ID, nls.localize('copyAsExpression', "Copy as Expression"), 20, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, undefined, '5_cutcopypaste'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, ADD_TO_WATCH_ID, nls.localize('addToWatchExpressions', "Add to Watch"), 100, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, undefined, 'z_commands'); diff --git a/src/vs/workbench/contrib/debug/browser/debugIcons.ts b/src/vs/workbench/contrib/debug/browser/debugIcons.ts index 50e3f86473393..ac91e22355ab4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugIcons.ts +++ b/src/vs/workbench/contrib/debug/browser/debugIcons.ts @@ -83,3 +83,5 @@ export const breakpointsActivate = registerIcon('breakpoints-activate', Codicon. export const debugConsoleEvaluationInput = registerIcon('debug-console-evaluation-input', Codicon.arrowSmallRight, localize('debugConsoleEvaluationInput', 'Icon for the debug evaluation input marker.')); export const debugConsoleEvaluationPrompt = registerIcon('debug-console-evaluation-prompt', Codicon.chevronRight, localize('debugConsoleEvaluationPrompt', 'Icon for the debug evaluation prompt.')); + +export const debugInspectMemory = registerIcon('debug-inspect-memory', Codicon.fileBinary, localize('debugInspectMemory', 'Icon for the inspect memory action.')); diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 7cdd149a3d716..e32056677e8c2 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -216,9 +216,21 @@ font-size: 11px; } +.debug-pane .monaco-list-row .expression { + display: flex; +} + +.debug-pane .monaco-list-row .expression .actionbar-spacer { + flex-grow: 1; +} + +.debug-pane .monaco-list-row .expression .value { + overflow: hidden; + white-space: pre; + text-overflow: ellipsis; +} + .debug-pane .monaco-list-row .expression .value.changed { - padding: 2px; - margin: 4px; border-radius: 4px; } @@ -230,6 +242,7 @@ .debug-pane .inputBoxContainer { box-sizing: border-box; flex-grow: 1; + display: none; } .debug-pane .debug-watch .monaco-inputbox { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 9eda83a19ba29..42f6f53c37b81 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -3,42 +3,43 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RunOnceScheduler } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; -import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IStackFrame, CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, IDataBreakpointInfoResponse, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, VARIABLES_VIEW_ID, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_CAN_VIEW_MEMORY } from 'vs/workbench/contrib/debug/common/debug'; -import { Variable, Scope, ErrorScope, StackFrame, Expression, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; -import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; -import { IAction } from 'vs/base/common/actions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; -import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IAsyncDataSource, ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { IAction } from 'vs/base/common/actions'; +import { coalesce } from 'vs/base/common/arrays'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { Codicon } from 'vs/base/common/codicons'; +import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { IMenuService, IMenu, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { localize } from 'vs/nls'; -import { Codicon } from 'vs/base/common/codicons'; -import { coalesce } from 'vs/base/common/arrays'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, CONTEXT_VARIABLES_FOCUSED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { ErrorScope, Expression, getUriForDebugMemory, Scope, StackFrame, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const $ = dom.$; let forgetScopes = true; @@ -59,14 +60,6 @@ export class VariablesView extends ViewPane { private tree!: WorkbenchAsyncDataTree; private savedViewState = new Map(); private autoExpandedScopes = new Set(); - private menu: IMenu; - private debugProtocolVariableMenuContext: IContextKey; - private breakWhenValueChangesSupported: IContextKey; - private breakWhenValueIsAccessedSupported: IContextKey; - private breakWhenValueIsReadSupported: IContextKey; - private variableEvaluateName: IContextKey; - private variableReadonly: IContextKey; - private viewMemorySupported: IContextKey; constructor( options: IViewletViewOptions, @@ -80,20 +73,10 @@ export class VariablesView extends ViewPane { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, - @IMenuService menuService: IMenuService + @IMenuService private readonly menuService: IMenuService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this.menu = menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService); - this._register(this.menu); - this.debugProtocolVariableMenuContext = CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT.bindTo(contextKeyService); - this.breakWhenValueChangesSupported = CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.bindTo(contextKeyService); - this.breakWhenValueIsAccessedSupported = CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.bindTo(contextKeyService); - this.breakWhenValueIsReadSupported = CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.bindTo(contextKeyService); - this.variableEvaluateName = CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT.bindTo(contextKeyService); - this.viewMemorySupported = CONTEXT_CAN_VIEW_MEMORY.bindTo(contextKeyService); - this.variableReadonly = CONTEXT_VARIABLE_IS_READONLY.bindTo(contextKeyService); - // Use scheduler to prevent unnecessary flashing this.updateTreeScheduler = new RunOnceScheduler(async () => { const stackFrame = this.debugService.getViewModel().focusedStackFrame; @@ -213,57 +196,89 @@ export class VariablesView extends ViewPane { private async onContextMenu(e: ITreeContextMenuEvent): Promise { const variable = e.element; - if (variable instanceof Variable && !!variable.value) { - this.debugProtocolVariableMenuContext.set(variable.variableMenuContext || ''); - variableInternalContext = variable; - const session = this.debugService.getViewModel().focusedSession; - this.variableEvaluateName.set(!!variable.evaluateName); - this.viewMemorySupported.set(!!session?.capabilities.supportsReadMemoryRequest && variable.memoryReference !== undefined); - const attributes = variable.presentationHint?.attributes; - this.variableReadonly.set(!!attributes && attributes.indexOf('readOnly') >= 0); - this.breakWhenValueChangesSupported.reset(); - this.breakWhenValueIsAccessedSupported.reset(); - this.breakWhenValueIsReadSupported.reset(); - if (session && session.capabilities.supportsDataBreakpoints) { - dataBreakpointInfoResponse = await session.dataBreakpointInfo(variable.name, variable.parent.reference); - const dataBreakpointId = dataBreakpointInfoResponse?.dataId; - const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes; - if (!dataBreakpointAccessTypes) { - // Assumes default behaviour: Supports breakWhenValueChanges - this.breakWhenValueChangesSupported.set(!!dataBreakpointId); - } else { - dataBreakpointAccessTypes.forEach(accessType => { - switch (accessType) { - case 'read': - this.breakWhenValueIsReadSupported.set(!!dataBreakpointId); - break; - case 'write': - this.breakWhenValueChangesSupported.set(!!dataBreakpointId); - break; - case 'readWrite': - this.breakWhenValueIsAccessedSupported.set(!!dataBreakpointId); - break; - } - }); - } - } + if (!(variable instanceof Variable) || !variable.value) { + return; + } - const context: IVariablesContext = { - sessionId: variable.getSession()?.getId(), - container: (variable.parent as (Variable | Scope)).toDebugProtocolObject(), - variable: variable.toDebugProtocolObject() - }; - const actions: IAction[] = []; - const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: context, shouldForwardArgs: false }, actions); + const toDispose = new DisposableStore(); + + try { + const contextKeyService = toDispose.add(await getContextForVariableMenuWithDataAccess(this.contextKeyService, variable)); + const menu = toDispose.add(this.menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService)); + + const context: IVariablesContext = getVariablesContext(variable); + const secondary: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary: [], secondary }, 'inline'); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => actions, + getActions: () => secondary, onHide: () => dispose(actionsDisposable) }); + } finally { + toDispose.dispose(); } } } +const getVariablesContext = (variable: Variable): IVariablesContext => ({ + sessionId: variable.getSession()?.getId(), + container: (variable.parent as (Variable | Scope)).toDebugProtocolObject(), + variable: variable.toDebugProtocolObject() +}); + +/** + * Gets a context key overlay that has context for the given variable, including data access info. + */ +async function getContextForVariableMenuWithDataAccess(parentContext: IContextKeyService, variable: Variable) { + const session = variable.getSession(); + if (!session || !session.capabilities.supportsDataBreakpoints) { + return getContextForVariableMenu(parentContext, variable); + } + + const contextKeys: [string, unknown][] = []; + dataBreakpointInfoResponse = await session.dataBreakpointInfo(variable.name, variable.parent.reference); + const dataBreakpointId = dataBreakpointInfoResponse?.dataId; + const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes; + + if (!dataBreakpointAccessTypes) { + contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]); + } else { + for (const accessType of dataBreakpointAccessTypes) { + switch (accessType) { + case 'read': + contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.key, !!dataBreakpointId]); + break; + case 'write': + contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]); + break; + case 'readWrite': + contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.key, !!dataBreakpointId]); + break; + } + } + } + + return getContextForVariableMenu(parentContext, variable, contextKeys); +} + +/** + * Gets a context key overlay that has context for the given variable. + */ +function getContextForVariableMenu(parentContext: IContextKeyService, variable: Variable, additionalContext: [string, unknown][] = []) { + const session = variable.getSession(); + const contextKeys: [string, unknown][] = [ + [CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT.key, variable.variableMenuContext || ''], + [CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT.key, !!variable.evaluateName], + [CONTEXT_CAN_VIEW_MEMORY.key, !!session?.capabilities.supportsReadMemoryRequest && variable.memoryReference !== undefined], + [CONTEXT_VARIABLE_IS_READONLY.key, !!variable.presentationHint?.attributes?.includes('readOnly')], + ...additionalContext, + ]; + + variableInternalContext = variable; + + return parentContext.createOverlay(contextKeys); +} + function isStackFrame(obj: any): obj is IStackFrame { return obj instanceof StackFrame; } @@ -371,6 +386,8 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { constructor( private readonly linkDetector: LinkDetector, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IDebugService debugService: IDebugService, @IContextViewService contextViewService: IContextViewService, @IThemeService themeService: IThemeService, @@ -409,6 +426,20 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { } }; } + + protected override renderActionBar(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData) { + const variable = expression as Variable; + const contextKeyService = getContextForVariableMenu(this.contextKeyService, variable); + const menu = this.menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService); + + const primary: IAction[] = []; + const context = getVariablesContext(variable); + data.elementDisposable.push(createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary, secondary: [] }, 'inline')); + + actionBar.clear(); + actionBar.context = context; + actionBar.push(primary, { icon: true, label: false }); + } } class VariablesAccessibilityProvider implements IListAccessibilityProvider { @@ -441,7 +472,7 @@ CommandsRegistry.registerCommand({ export const COPY_VALUE_ID = 'workbench.debug.viewlet.action.copyValue'; CommandsRegistry.registerCommand({ id: COPY_VALUE_ID, - handler: async (accessor: ServicesAccessor, arg: Variable | Expression | unknown, ctx?: (Variable | Expression)[]) => { + handler: async (accessor: ServicesAccessor, arg: Variable | Expression | IVariablesContext, ctx?: (Variable | Expression)[]) => { const debugService = accessor.get(IDebugService); const clipboardService = accessor.get(IClipboardService); let elementContext = ''; @@ -488,40 +519,20 @@ CommandsRegistry.registerCommand({ return; } - const debugService = accessor.get(IDebugService); const commandService = accessor.get(ICommandService); const editorService = accessor.get(IEditorService); const ext = await accessor.get(IExtensionService).getExtension(HEX_EDITOR_EXTENSION_ID); if (!ext) { await commandService.executeCommand('workbench.extensions.search', `@id:${HEX_EDITOR_EXTENSION_ID}`); - return; - } - - const pane = await editorService.openEditor({ - resource: getUriForDebugMemory(arg.sessionId, arg.variable.memoryReference), - options: { - revealIfOpened: true, - override: HEX_EDITOR_EDITOR_ID, - }, - }); - - const editor = pane?.input; - if (!editor) { - return; + } else { + await editorService.openEditor({ + resource: getUriForDebugMemory(arg.sessionId, arg.variable.memoryReference), + options: { + revealIfOpened: true, + override: HEX_EDITOR_EDITOR_ID, + }, + }); } - - const disposable = new DisposableStore(); - disposable.add(editor); - disposable.add(debugService.onDidEndSession(session => { - if (session.getId() === arg.sessionId) { - disposable.dispose(); - } - })); - disposable.add(editorService.onDidCloseEditor(e => { - if (e.editor === editor) { - disposable.dispose(); - } - })); } }); From f0054e049cd286ff317c738efe0fd8810b48ae98 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 4 Jan 2022 20:59:28 -0800 Subject: [PATCH 0999/2210] Add `explorer.enableUndo` config to warn/allow/disable explorer undo (#140140) Closes #117621 --- .../contrib/files/browser/fileActions.ts | 7 +++--- .../contrib/files/browser/fileImportExport.ts | 11 +++++---- .../files/browser/files.contribution.ts | 24 ++++++++++++++++--- .../workbench/contrib/files/common/files.ts | 7 ++++++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index b3e8ad245f2ce..16cc54f9b6904 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Action } from 'vs/base/common/actions'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { VIEWLET_ID, IFilesConfiguration, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, IFilesConfiguration, VIEW_ID, UndoEnablement } from 'vs/workbench/contrib/files/common/files'; import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; @@ -1037,10 +1037,11 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { } else { const resourceFileEdits = sourceTargetPairs.map(pair => new ResourceFileEdit(pair.source, pair.target, { copy: true })); const options = { + confirmBeforeUndo: configurationService.getValue().explorer.enableUndo === UndoEnablement.Warn, progressLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'copyingBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Copying {0} files", sourceTargetPairs.length) : nls.localize({ key: 'copyingFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Copying {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)), - undoLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'copyBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Copy {0} files", sourceTargetPairs.length) - : nls.localize({ key: 'copyFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Copy {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) + undoLabel: sourceTargetPairs.length > 1 ? nls.localize({ key: 'copyBulkEdit', comment: ['Placeholder will be replaced by the number of files being copied'] }, "Paste {0} files", sourceTargetPairs.length) + : nls.localize({ key: 'copyFileBulkEdit', comment: ['Placeholder will be replaced by the name of the file copied.'] }, "Paste {0}", resources.basenameOrAuthority(sourceTargetPairs[0].target)) }; await explorerService.applyBulkEdit(resourceFileEdits, options); } diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts index 84e56881796f9..c054249caa61d 100644 --- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts +++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts @@ -10,7 +10,7 @@ import { ByteSize, FileSystemProviderCapabilities, IFileService, IFileStatWithMe import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IProgress, IProgressService, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; -import { VIEW_ID } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, UndoEnablement, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Limiter, Promises, RunOnceWorker } from 'vs/base/common/async'; import { newWriteableBufferStream, VSBuffer } from 'vs/base/common/buffer'; @@ -32,6 +32,7 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { once } from 'vs/base/common/functional'; import { coalesce } from 'vs/base/common/arrays'; import { canceled } from 'vs/base/common/errors'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; //#region Browser File Upload (drag and drop, input element) @@ -387,6 +388,7 @@ export class ExternalFileImport { @IFileService private readonly fileService: IFileService, @IHostService private readonly hostService: IHostService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IDialogService private readonly dialogService: IDialogService, @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, @IExplorerService private readonly explorerService: IExplorerService, @@ -530,12 +532,13 @@ export class ExternalFileImport { await this.explorerService.applyBulkEdit(resourceFileEdits, { undoLabel: resourcesFiltered.length === 1 ? - localize('copyFile', "Copy {0}", basename(resourcesFiltered[0])) : - localize('copynFile', "Copy {0} resources", resourcesFiltered.length), + localize('importFile', "Import {0}", basename(resourcesFiltered[0])) : + localize('importnFile', "Import {0} resources", resourcesFiltered.length), progressLabel: resourcesFiltered.length === 1 ? localize('copyingFile', "Copying {0}", basename(resourcesFiltered[0])) : localize('copyingnFile', "Copying {0} resources", resourcesFiltered.length), - progressLocation: ProgressLocation.Window + progressLocation: ProgressLocation.Window, + confirmBeforeUndo: this.configurationService.getValue().explorer.enableUndo === UndoEnablement.Warn, }); // if we only add one file, just open it directly diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 4599241012b31..b15053083b8da 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -10,7 +10,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IFileEditorInput, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; -import { SortOrder, LexicographicOptions, FILE_EDITOR_INPUT_ID, BINARY_TEXT_FILE_MODE } from 'vs/workbench/contrib/files/common/files'; +import { SortOrder, LexicographicOptions, FILE_EDITOR_INPUT_ID, BINARY_TEXT_FILE_MODE, UndoEnablement, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput'; @@ -34,6 +34,7 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { FileEditorInputSerializer, FileEditorWorkingCopyEditorHandler } from 'vs/workbench/contrib/files/browser/editors/fileEditorHandler'; import { ModesRegistry } from 'vs/editor/common/languages/modesRegistry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; class FileUriLabelContribution implements IWorkbenchContribution { @@ -369,6 +370,17 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('confirmDelete', "Controls whether the explorer should ask for confirmation when deleting a file via the trash."), 'default': true }, + 'explorer.enableUndo': { + 'type': 'string', + 'enum': [UndoEnablement.Warn, UndoEnablement.Allow, UndoEnablement.Disable], + 'description': nls.localize('confirmUndo', "Controls how the explorer participates in undoing file and folder edits."), + 'default': UndoEnablement.Warn, + 'enumDescriptions': [ + nls.localize('enableUndo.warn', 'Explorer will prompt before undoing all file and folder creation events.'), + nls.localize('enableUndo.allow', 'Explorer will undo file and folder creation events without prompting.'), + nls.localize('enableUndo.disable', 'Explorer does not participte in undo events.'), + ], + }, 'explorer.expandSingleFolderWorkspaces': { 'type': 'boolean', 'description': nls.localize('expandSingleFolderWorkspaces', "Controls whether the explorer should expand multi-root workspaces containing only one folder during initilization"), @@ -445,7 +457,10 @@ configurationRegistry.registerConfiguration({ UndoCommand.addImplementation(110, 'explorer', (accessor: ServicesAccessor) => { const undoRedoService = accessor.get(IUndoRedoService); const explorerService = accessor.get(IExplorerService); - if (explorerService.hasViewFocus() && undoRedoService.canUndo(UNDO_REDO_SOURCE)) { + const configurationService = accessor.get(IConfigurationService); + + const explorerCanUndo = configurationService.getValue().explorer.enableUndo !== UndoEnablement.Disable; + if (explorerService.hasViewFocus() && undoRedoService.canUndo(UNDO_REDO_SOURCE) && explorerCanUndo) { undoRedoService.undo(UNDO_REDO_SOURCE); return true; } @@ -456,7 +471,10 @@ UndoCommand.addImplementation(110, 'explorer', (accessor: ServicesAccessor) => { RedoCommand.addImplementation(110, 'explorer', (accessor: ServicesAccessor) => { const undoRedoService = accessor.get(IUndoRedoService); const explorerService = accessor.get(IExplorerService); - if (explorerService.hasViewFocus() && undoRedoService.canRedo(UNDO_REDO_SOURCE)) { + const configurationService = accessor.get(IConfigurationService); + + const explorerCanUndo = configurationService.getValue().explorer.enableUndo !== UndoEnablement.Disable; + if (explorerService.hasViewFocus() && undoRedoService.canRedo(UNDO_REDO_SOURCE) && explorerCanUndo) { undoRedoService.redo(UNDO_REDO_SOURCE); return true; } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 32d92f635dc68..010a811838a0b 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -88,6 +88,7 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb autoReveal: boolean | 'focusNoScroll'; enableDragAndDrop: boolean; confirmDelete: boolean; + enableUndo: UndoEnablement; expandSingleFolderWorkspaces: boolean; sortOrder: SortOrder; sortOrderLexicographicOptions: LexicographicOptions; @@ -113,6 +114,12 @@ export const enum SortOrder { Modified = 'modified' } +export const enum UndoEnablement { + Warn = 'warn', + Allow = 'allow', + Disable = 'disable', +} + export const enum LexicographicOptions { Default = 'default', Upper = 'upper', From 12f2f605512ea42ed926f30d9c2adc8cba543816 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 5 Jan 2022 06:58:50 +0100 Subject: [PATCH 1000/2210] Opening edt active when ctrl+tab without editors feels wrong (fix #140102) --- src/vs/workbench/browser/layout.ts | 19 +++++++++++-------- .../parts/editor/editor.contribution.ts | 6 +++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 9f7d134b55918..a9fa1ef7a3519 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -129,8 +129,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi readonly onDidLayout = this._onDidLayout.event; //#endregion + + //#region Properties + readonly hasContainer = true; - readonly container: HTMLElement = document.createElement('div'); + readonly container = document.createElement('div'); private _dimension!: IDimension; get dimension(): IDimension { return this._dimension; } @@ -148,13 +151,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }; } + //#endregion + private readonly parts = new Map(); - private _initialized: boolean = false; + private initialized = false; private workbenchGrid!: SerializableGrid; - private disposed: boolean | undefined; - private titleBarPartView!: ISerializableView; private bannerPartView!: ISerializableView; private activityBarPartView!: ISerializableView; @@ -185,6 +188,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private windowState!: IWorkbenchLayoutWindowState; private stateModel!: LayoutStateModel; + private disposed = false; + constructor( protected readonly parent: HTMLElement ) { @@ -904,7 +909,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } isVisible(part: Parts): boolean { - if (this._initialized) { + if (this.initialized) { switch (part) { case Parts.TITLEBAR_PART: return this.workbenchGrid.isViewVisible(this.titleBarPartView); @@ -1233,7 +1238,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout the grid widget this.workbenchGrid.layout(this._dimension.width, this._dimension.height); - this._initialized = true; + this.initialized = true; // Emit as event this._onDidLayout.fire(this._dimension); @@ -1691,7 +1696,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } updateMenubarVisibility(skipLayout: boolean): void { - // Layout const shouldShowTitleBar = this.shouldShowTitleBar(); if (!skipLayout && this.workbenchGrid && shouldShowTitleBar !== this.isVisible(Parts.TITLEBAR_PART)) { this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowTitleBar); @@ -1723,7 +1727,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.setPanelHidden(false); } - const panelPart = this.getPart(Parts.PANEL_PART); const oldPositionValue = positionToString(this.getPanelPosition()); const newPositionValue = positionToString(position); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 3edee89dc2a71..1317892c14e09 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { IEditorFactoryRegistry, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorExtensions, EditorGroupEditorsCountContext, - ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext + ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorGroupEmptyContext } from 'vs/workbench/common/editor'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -257,8 +257,8 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorTypeActio registry.registerWorkbenchAction(SyncActionDescriptor.from(ReOpenInTextEditorAction), 'View: Reopen Editor With Text Editor', CATEGORIES.View.value, ActiveEditorAvailableEditorIdsContext); registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessPreviousRecentlyUsedEditorAction), 'View: Quick Open Previous Recently Used Editor', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessLeastRecentlyUsedEditorAction), 'View: Quick Open Least Recently Used Editor', CATEGORIES.View.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessPreviousRecentlyUsedEditorInGroupAction, { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }), 'View: Quick Open Previous Recently Used Editor in Group', CATEGORIES.View.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessLeastRecentlyUsedEditorInGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }), 'View: Quick Open Least Recently Used Editor in Group', CATEGORIES.View.value); +registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessPreviousRecentlyUsedEditorInGroupAction, { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }, ActiveEditorGroupEmptyContext.toNegated()), 'View: Quick Open Previous Recently Used Editor in Group', CATEGORIES.View.value); +registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessLeastRecentlyUsedEditorInGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }, ActiveEditorGroupEmptyContext.toNegated()), 'View: Quick Open Least Recently Used Editor in Group', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessPreviousEditorFromHistoryAction), 'Quick Open Previous Editor from History'); const quickAccessNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; From 147e502c3de33d40124c00a85e58537646088fe9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Jan 2022 08:02:58 +0100 Subject: [PATCH 1001/2210] support open to side gesture, https://github.com/microsoft/vscode/issues/129528 --- src/vs/editor/contrib/inlayHints/inlayHintsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 13d76d9c8ccf5..ee265bf361d7c 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -230,7 +230,7 @@ export class InlayHintsController implements IEditorContribution { } const options = e.target.detail?.injectedText?.options; if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLink) { - this._openerService.open(options.attachedData.href, { allowCommands: true }); + this._openerService.open(options.attachedData.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); } })); From 26ed170af8ac0ddf3946d4350cd3e3a28ae8248e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Jan 2022 08:43:49 +0100 Subject: [PATCH 1002/2210] use decoration for active inlay hints link, support cusor decoration fyi @hediet, https://github.com/microsoft/vscode/issues/129528 --- src/vs/editor/browser/editorDom.ts | 2 +- .../inlayHints/inlayHintsController.ts | 37 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index 8a08c62fd012f..94d86f8e765d2 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -338,7 +338,7 @@ export interface CssProperties { backgroundColor?: string | ThemeColor; opacity?: string; verticalAlign?: string; - + cursor?: string; margin?: string; padding?: string; width?: string; diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index ee265bf361d7c..e84e2e18f4e35 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -104,7 +104,7 @@ class InlayHintsCache { } class InlayHintLink { - constructor(readonly href: string) { } + constructor(readonly href: string, readonly index: number, readonly hint: InlayHint) { } } export class InlayHintsController implements IEditorContribution { @@ -120,6 +120,7 @@ export class InlayHintsController implements IEditorContribution { private readonly _cache = new InlayHintsCache(); private readonly _decorationsMetadata = new Map(); private readonly _ruleFactory = new DynamicCssRules(this._editor); + private _activeInlayHintLink?: InlayHintLink; constructor( private readonly _editor: ICodeEditor, @@ -209,18 +210,24 @@ export class InlayHintsController implements IEditorContribution { undoHover(); return; } + const model = this._editor.getModel()!; const options = mouseEvent.target.detail?.injectedText?.options; if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLink) { - if (mouseEvent.target.element instanceof HTMLElement) { - // todo@jrieken not proper, won't work with wrapped lines. use decoration instead - mouseEvent.target.element.style.cursor = 'pointer'; - mouseEvent.target.element.style.color = `var(${colors.asCssVariableName(colors.editorActiveLinkForeground)})`; - undoHover = () => { - (mouseEvent.target.element).style.cursor = ''; - (mouseEvent.target.element).style.color = ''; - undoHover = () => { }; - }; + this._activeInlayHintLink = options.attachedData; + + const lineNumber = this._activeInlayHintLink.hint.position.lineNumber; + const range = new Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber)); + const lineHints = new Set(); + for (let data of this._decorationsMetadata.values()) { + if (range.containsPosition(data.hint.position)) { + lineHints.add(data.hint); + } } + this._updateHintsDecorators([range], Array.from(lineHints)); + undoHover = () => { + this._activeInlayHintLink = undefined; + this._updateHintsDecorators([range], Array.from(lineHints)); + }; } })); this._sessionDisposables.add(gesture.onCancel(undoHover)); @@ -233,7 +240,6 @@ export class InlayHintsController implements IEditorContribution { this._openerService.open(options.attachedData.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); } })); - } private _getHintsRanges(): Range[] { @@ -304,7 +310,12 @@ export class InlayHintsController implements IEditorContribution { if (isLink) { cssProperties.textDecoration = 'underline'; - // cssProperties.cursor = 'pointer'; + + if (this._activeInlayHintLink?.hint === hint && this._activeInlayHintLink.index === i && this._activeInlayHintLink.href === node.href) { + // active link! + cssProperties.cursor = 'pointer'; + cssProperties.color = themeColorFromId(colors.editorActiveLinkForeground); + } } if (isFirst && isLast) { @@ -338,7 +349,7 @@ export class InlayHintsController implements IEditorContribution { content: fixSpace(isLink ? node.label : node), inlineClassNameAffectsLetterSpacing: true, inlineClassName: classNameRef.className, - attachedData: isLink ? new InlayHintLink(node.href) : undefined + attachedData: isLink ? new InlayHintLink(node.href, i, hint) : undefined } as InjectedTextOptions, description: 'InlayHint', showIfCollapsed: !usesWordRange, From 82855424a640049182369c61129d80d600e597b6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 5 Jan 2022 08:49:38 +0100 Subject: [PATCH 1003/2210] watcher - more tests --- .../node/nodejsWatcher.integrationTest.ts | 74 ++++++++++++------- .../node/parcelWatcher.integrationTest.ts | 58 +++++++-------- 2 files changed, 76 insertions(+), 56 deletions(-) diff --git a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts index aecad4dcb4dfa..c129369151591 100644 --- a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts @@ -12,13 +12,15 @@ import { FileChangeType } from 'vs/platform/files/common/files'; import { IDiskFileChange } from 'vs/platform/files/common/watcher'; import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { getDriveLetter } from 'vs/base/common/extpath'; +import { ltrim } from 'vs/base/common/strings'; // this suite has shown flaky runs in Azure pipelines where // tasks would just hang and timeout after a while (not in // mocha but generally). as such they will run only on demand // whenever we update the watcher library. -((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('Recursive File Watcher (node.js)', () => { +((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('File Watcher (node.js)', () => { let testDir: string; let watcher: NodeJSFileWatcher; @@ -336,12 +338,29 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; await createWatcher(link); + return basicCrudTest(join(link, 'newFile.txt')); + }); + + async function basicCrudTest(filePath: string, skipAdd?: boolean): Promise { + let changeFuture: Promise; + // New file - const newFilePath = join(link, 'newFile.txt'); - let changeFuture: Promise = awaitEvent(event, newFilePath, FileChangeType.ADDED); - await Promises.writeFile(newFilePath, 'Hello World'); + if (!skipAdd) { + changeFuture = awaitEvent(event, filePath, FileChangeType.ADDED); + await Promises.writeFile(filePath, 'Hello World'); + await changeFuture; + } + + // Change file + changeFuture = awaitEvent(event, filePath, FileChangeType.UPDATED); + await Promises.writeFile(filePath, 'Hello Change'); await changeFuture; - }); + + // Delete file + changeFuture = awaitEvent(event, filePath, FileChangeType.DELETED); + await Promises.unlink(await Promises.realpath(filePath)); // support symlinks + await changeFuture; + } (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (file watch)', async function () { const link = join(testDir, 'lorem.txt-linked'); @@ -350,41 +369,42 @@ import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; await createWatcher(link); - // Change file - let changeFuture = awaitEvent(event, link, FileChangeType.UPDATED); - await Promises.writeFile(link, 'Hello Change'); - await changeFuture; + return basicCrudTest(link, true); + }); - // Delete file - changeFuture = awaitEvent(event, link, FileChangeType.DELETED); - await Promises.unlink(linkTarget); - await changeFuture; + (!isWindows /* UNC is windows only */ ? test.skip : test)('unc support (folder watch)', async function () { + + // Local UNC paths are in the form of: \\localhost\c$\my_dir + const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}`; + + await createWatcher(uncPath); + + return basicCrudTest(join(uncPath, 'newFile.txt')); + }); + + (!isWindows /* UNC is windows only */ ? test.skip : test)('unc support (file watch)', async function () { + + // Local UNC paths are in the form of: \\localhost\c$\my_dir + const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}\\lorem.txt`; + + await createWatcher(uncPath); + + return basicCrudTest(uncPath, true); }); (isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing (folder watch)', async function () { const wrongCase = join(dirname(testDir), basename(testDir).toUpperCase()); + await createWatcher(wrongCase); - // New file - const newFilePath = join(wrongCase, 'newFile.txt'); - let changeFuture: Promise = awaitEvent(event, newFilePath, FileChangeType.ADDED); - await Promises.writeFile(newFilePath, 'Hello World'); - await changeFuture; + return basicCrudTest(join(wrongCase, 'newFile.txt')); }); (isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing (file watch)', async function () { const filePath = join(testDir, 'LOREM.txt'); await createWatcher(filePath); - // Change file - let changeFuture = awaitEvent(event, filePath, FileChangeType.UPDATED); - await Promises.writeFile(filePath, 'Hello Change'); - await changeFuture; - - // Delete file - changeFuture = awaitEvent(event, filePath, FileChangeType.DELETED); - await Promises.unlink(filePath); - await changeFuture; + return basicCrudTest(filePath, true); }); test('invalid path does not explode', async function () { diff --git a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts index 777152eb95000..83c0b4d28dd69 100644 --- a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts @@ -14,13 +14,15 @@ import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/tes import { FileChangeType } from 'vs/platform/files/common/files'; import { IParcelWatcherInstance, ParcelWatcher } from 'vs/platform/files/node/watcher/parcel/parcelWatcher'; import { IWatchRequest } from 'vs/platform/files/common/watcher'; +import { getDriveLetter } from 'vs/base/common/extpath'; +import { ltrim } from 'vs/base/common/strings'; // this suite has shown flaky runs in Azure pipelines where // tasks would just hang and timeout after a while (not in // mocha but generally). as such they will run only on demand // whenever we update the watcher library. -((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('Recursive File Watcher (parcel)', () => { +((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('File Watcher (parcel)', () => { class TestParcelWatcher extends ParcelWatcher { @@ -291,22 +293,26 @@ import { IWatchRequest } from 'vs/platform/files/common/watcher'; (!isLinux /* polling is only used in linux environments (WSL) */ ? test.skip : test)('basics (polling)', async function () { await watcher.watch([{ path: testDir, excludes: [], pollingInterval: 100 }]); + return basicCrudTest(join(testDir, 'deep', 'newFile.txt')); + }); + + async function basicCrudTest(filePath: string): Promise { + // New file - const newFilePath = join(testDir, 'deep', 'newFile.txt'); - let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); - await Promises.writeFile(newFilePath, 'Hello World'); + let changeFuture: Promise = awaitEvent(watcher, filePath, FileChangeType.ADDED); + await Promises.writeFile(filePath, 'Hello World'); await changeFuture; // Change file - changeFuture = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED); - await Promises.writeFile(newFilePath, 'Hello Change'); + changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED); + await Promises.writeFile(filePath, 'Hello Change'); await changeFuture; // Delete file - changeFuture = awaitEvent(watcher, newFilePath, FileChangeType.DELETED); - await Promises.unlink(newFilePath); + changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED); + await Promises.unlink(filePath); await changeFuture; - }); + } test('multiple events', async function () { await watcher.watch([{ path: testDir, excludes: [] }]); @@ -427,11 +433,7 @@ import { IWatchRequest } from 'vs/platform/files/common/watcher'; await watcher.watch([{ path: testDir, excludes: [realpathSync(testDir)] }]); await watcher.watch([{ path: testDir, excludes: [] }]); - // New file (*.txt) - let newTextFilePath = join(testDir, 'deep', 'newFile.txt'); - let changeFuture: Promise = awaitEvent(watcher, newTextFilePath, FileChangeType.ADDED); - await Promises.writeFile(newTextFilePath, 'Hello World'); - await changeFuture; + return basicCrudTest(join(testDir, 'deep', 'newFile.txt')); }); (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (root)', async function () { @@ -441,11 +443,7 @@ import { IWatchRequest } from 'vs/platform/files/common/watcher'; await watcher.watch([{ path: link, excludes: [] }]); - // New file - const newFilePath = join(link, 'newFile.txt'); - let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); - await Promises.writeFile(newFilePath, 'Hello World'); - await changeFuture; + return basicCrudTest(join(link, 'newFile.txt')); }); (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (via extra watch)', async function () { @@ -455,11 +453,17 @@ import { IWatchRequest } from 'vs/platform/files/common/watcher'; await watcher.watch([{ path: testDir, excludes: [] }, { path: link, excludes: [] }]); - // New file - const newFilePath = join(link, 'newFile.txt'); - let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); - await Promises.writeFile(newFilePath, 'Hello World'); - await changeFuture; + return basicCrudTest(join(link, 'newFile.txt')); + }); + + (!isWindows /* UNC is windows only */ ? test.skip : test)('unc support', async function () { + + // Local UNC paths are in the form of: \\localhost\c$\my_dir + const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}`; + + await watcher.watch([{ path: uncPath, excludes: [] }]); + + return basicCrudTest(join(uncPath, 'deep', 'newFile.txt')); }); (isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing', async function () { @@ -467,11 +471,7 @@ import { IWatchRequest } from 'vs/platform/files/common/watcher'; await watcher.watch([{ path: deepWrongCasedPath, excludes: [] }]); - // New file - const newFilePath = join(deepWrongCasedPath, 'newFile.txt'); - let changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.ADDED); - await Promises.writeFile(newFilePath, 'Hello World'); - await changeFuture; + return basicCrudTest(join(deepWrongCasedPath, 'newFile.txt')); }); test('invalid folder does not explode', async function () { From 4e8d9b513b4f64ad1a4b5a09ec50081dc6ad1cbe Mon Sep 17 00:00:00 2001 From: yash621 Date: Wed, 5 Jan 2022 14:08:02 +0530 Subject: [PATCH 1004/2210] updated error messages --- src/vs/platform/launch/electron-main/launchMainService.ts | 2 +- .../contrib/remote/electron-sandbox/remote.contribution.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 9fcef6cd9b641..3ad5aac15a5dd 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -266,7 +266,7 @@ export class LaunchMainService implements ILaunchMainService { }); setTimeout(() => { - resolve({ hostName: remoteAuthority, errorMessage: `Fetching remote diagnostics for '${remoteAuthority}' timed out.` }); + resolve({ hostName: remoteAuthority, errorMessage: `Connection to 'SSH:${remoteAuthority}' could not be established` }); }, 5000); } else { resolve(undefined); diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts index 110b90af3142c..3ebb28cc0bb2f 100644 --- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts @@ -66,7 +66,7 @@ class RemoteAgentDiagnosticListener implements IWorkbenchContribution { ipcRenderer.send(request.replyChannel, info); }) .catch(e => { - const errorMessage = e && e.message ? `Fetching remote diagnostics for '${hostName}' failed: ${e.message}` : `Fetching remote diagnostics for '${hostName}' failed.`; + const errorMessage = e && e.message ? `Connection to 'SSH: ${hostName}' could not be established ${e.message}` : `Connection to 'SSH: ${hostName}' could not be established `; ipcRenderer.send(request.replyChannel, { hostName, errorMessage }); }); } else { From 23039b6289bc5f46532bd4730ceb67c8b319d43c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 5 Jan 2022 10:14:37 +0100 Subject: [PATCH 1005/2210] watcher - more robust root path delete handling --- .../node/watcher/nodejs/nodejsWatcher.ts | 253 +++++++++++------- .../node/nodejsWatcher.integrationTest.ts | 25 +- 2 files changed, 172 insertions(+), 106 deletions(-) diff --git a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts index 8dfa7fc710334..cf99f96381e80 100644 --- a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts @@ -23,7 +23,7 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche // atomic save operations where a tool may chose // to delete a file before creating it again for // an update. - private static readonly FILE_DELETE_HANDLER_DELAY = 25; + private static readonly FILE_DELETE_HANDLER_DELAY = 100; // A delay for collecting file changes from node.js // before collecting them for coalescing and emitting @@ -65,6 +65,8 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche } catch (error) { if (error.code !== 'ENOENT') { this.error(error); + } else { + this.trace(error); } } } @@ -112,6 +114,7 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche let disposables = new DisposableStore(); try { + const pathBasename = basename(path); // Creating watcher can fail with an exception const watcher = watch(path); @@ -142,6 +145,10 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche watcher.on('error', (code: number, signal: string) => { this.error(`Failed to watch ${path} for changes using fs.watch() (${code}, ${signal})`); + + // The watcher is no longer functional reliably + // so we go ahead and dispose it + this.dispose(); }); watcher.on('change', (type, raw) => { @@ -149,7 +156,7 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche return; // ignore if already disposed } - this.trace(`["${type}"] ${raw} (fs.watch() raw event)`); + this.trace(`[raw] ["${type}"] ${raw}`); // Normalize file name let changedFileName = ''; @@ -166,52 +173,10 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche return; // ignore unexpected events } - // File - if (!isDirectory) { - if (type === 'rename' || changedFileName !== basename(path)) { - - // The file was either deleted or renamed. Many tools apply changes to files in an - // atomic way ("Atomic Save") by first renaming the file to a temporary name and then - // renaming it back to the original name. Our watcher will detect this as a rename - // and then stops to work on Mac and Linux because the watcher is applied to the - // inode and not the name. The fix is to detect this case and trying to watch the file - // again after a certain delay. - // In addition, we send out a delete event if after a timeout we detect that the file - // does indeed not exist anymore. - - const timeoutHandle = setTimeout(async () => { - const fileExists = await Promises.exists(path); - - if (cts.token.isCancellationRequested) { - return; // ignore if disposed by now - } - - // File still exists, so emit as change event and reapply the watcher - if (fileExists) { - this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }); - - disposables.add(await this.doWatch(path, false)); - } - - // File seems to be really gone, so emit a deleted event - else { - this.onFileChange({ path: this.request.path, type: FileChangeType.DELETED }); - } - }, NodeJSFileWatcher.FILE_DELETE_HANDLER_DELAY); - - // Very important to dispose the watcher which now points to a stale inode - // and wire in a new disposable that tracks our timeout that is installed - disposables.clear(); - disposables.add(toDisposable(() => clearTimeout(timeoutHandle))); - } else { - this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }); - } - } - // Folder - else { + if (isDirectory) { - // Children add/delete + // Folder child added/deleted if (type === 'rename') { // Cancel any previous stats for this file if existing @@ -222,57 +187,71 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche const timeoutHandle = setTimeout(async () => { mapPathToStatDisposable.delete(changedFileName); - // fs.watch() does not really help us figuring out - // if the root folder got deleted. As such we have - // to check if our watched path still exists and - // handle that accordingly. The only hint we get - // is that the event file name will be the same - // as the folder we are watching... + // Depending on the OS the watcher runs on, there + // is different behaviour for when the watched + // folder path is being deleted: + // + // - macOS: not reported but events continue to + // work even when the folder is brought + // back, though it seems every change + // to a file is reported as "rename" + // - Linux: "rename" event is reported with the + // name of the folder and events stop + // working + // - Windows: an EPERM error is thrown that we + // handle from the `on('error')` event // // We do not re-attach the watcher after timeout // though as we do for file watches because for // file watching specifically we want to handle - // the atomic-write cases. - if (changedFileName === basename(path) && !await Promises.exists(path)) { - this.onFileChange({ path: this.request.path, type: FileChangeType.DELETED }); - } + // the atomic-write cases where the file is being + // deleted and recreated with different contents. + // + // Same as with recursive watching, we do not + // emit a delete event in this case. + if (changedFileName === pathBasename && !await Promises.exists(path)) { + this.warn('Watcher shutdown because watched path got deleted'); - else { + // The watcher is no longer functional reliably + // so we go ahead and dispose it + this.dispose(); - // In order to properly detect renames on a case-insensitive - // file system, we need to use `existsChildStrictCase` helper - // because otherwise we would wrongly assume a file exists - // when it was renamed in the old form. - const fileExists = await this.existsChildStrictCase(join(path, changedFileName)); + return; + } - if (cts.token.isCancellationRequested) { - return; // ignore if disposed by now - } + // In order to properly detect renames on a case-insensitive + // file system, we need to use `existsChildStrictCase` helper + // because otherwise we would wrongly assume a file exists + // when it was renamed to same name but different case. + const fileExists = await this.existsChildStrictCase(join(path, changedFileName)); + + if (cts.token.isCancellationRequested) { + return; // ignore if disposed by now + } - // Figure out the correct event type: - // File Exists: either 'added' or 'updated' if known before - // File Does not Exist: always 'deleted' - let type: FileChangeType; - if (fileExists) { - if (folderChildren.has(changedFileName)) { - type = FileChangeType.UPDATED; - } else { - type = FileChangeType.ADDED; - folderChildren.add(changedFileName); - } + // Figure out the correct event type: + // File Exists: either 'added' or 'updated' if known before + // File Does not Exist: always 'deleted' + let type: FileChangeType; + if (fileExists) { + if (folderChildren.has(changedFileName)) { + type = FileChangeType.UPDATED; } else { - folderChildren.delete(changedFileName); - type = FileChangeType.DELETED; + type = FileChangeType.ADDED; + folderChildren.add(changedFileName); } - - this.onFileChange({ path: join(this.request.path, changedFileName), type }); + } else { + folderChildren.delete(changedFileName); + type = FileChangeType.DELETED; } + + this.onFileChange({ path: join(this.request.path, changedFileName), type }); }, NodeJSFileWatcher.FILE_DELETE_HANDLER_DELAY); mapPathToStatDisposable.set(changedFileName, toDisposable(() => clearTimeout(timeoutHandle))); } - // Other events + // Folder child changed else { // Figure out the correct event type: if this is the @@ -288,6 +267,70 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche this.onFileChange({ path: join(this.request.path, changedFileName), type }); } } + + // File + else { + + // File added/deleted + if (type === 'rename' || changedFileName !== pathBasename) { + + // Depending on the OS the watcher runs on, there + // is different behaviour for when the watched + // file path is being deleted: + // + // - macOS: "rename" event is reported and events + // stop working + // - Linux: "rename" event is reported and events + // stop working + // - Windows: "rename" event is reported and events + // continue to work when file is restored + // + // As opposed to folder watching, we re-attach the + // watcher after brief timeout to support "atomic save" + // operations where a tool may decide to delete a file + // and then create it with the updated contents. + // + // Different to folder watching, we emit a delete event + // though we never detect when the file is brought back + // because the watcher is disposed then. + + const timeoutHandle = setTimeout(async () => { + const fileExists = await Promises.exists(path); + + if (cts.token.isCancellationRequested) { + return; // ignore if disposed by now + } + + // File still exists, so emit as change event and reapply the watcher + if (fileExists) { + this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }); + + disposables.add(await this.doWatch(path, false)); + } + + // File seems to be really gone, so emit a deleted event and dispose + else { + const eventPromise = this.onFileChange({ path: this.request.path, type: FileChangeType.DELETED }); + + // Important to await the event delivery + // before disposing the watcher, otherwise + // we will loose this event. + await eventPromise; + this.dispose(); + } + }, NodeJSFileWatcher.FILE_DELETE_HANDLER_DELAY); + + // Very important to dispose the watcher which now points to a stale inode + // and wire in a new disposable that tracks our timeout that is installed + disposables.clear(); + disposables.add(toDisposable(() => clearTimeout(timeoutHandle))); + } + + // File changed + else { + this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }); + } + } }); } catch (error) { if (await Promises.exists(path) && !cts.token.isCancellationRequested) { @@ -301,7 +344,7 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche }); } - private onFileChange(event: IDiskFileChange): void { + private async onFileChange(event: IDiskFileChange): Promise { if (this.cts.token.isCancellationRequested) { return; } @@ -321,38 +364,44 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche } // Handle emit through delayer to accommodate for bulk changes and thus reduce spam - this.fileChangesDelayer.trigger(async () => { - const fileChanges = this.fileChangesBuffer; - this.fileChangesBuffer = []; + try { + await this.fileChangesDelayer.trigger(async () => { + const fileChanges = this.fileChangesBuffer; + this.fileChangesBuffer = []; - // Coalesce events: merge events of same kind - const coalescedFileChanges = coalesceEvents(fileChanges); + // Coalesce events: merge events of same kind + const coalescedFileChanges = coalesceEvents(fileChanges); - // Logging - if (this.verboseLogging) { - for (const event of coalescedFileChanges) { - this.trace(`>> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); + // Logging + if (this.verboseLogging) { + for (const event of coalescedFileChanges) { + this.trace(`>> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); + } } - } - // Broadcast to clients - if (coalescedFileChanges.length > 0) { - this.onDidFilesChange(coalescedFileChanges); - } - }).catch(() => { + // Broadcast to clients + if (coalescedFileChanges.length > 0) { + this.onDidFilesChange(coalescedFileChanges); + } + }); + } catch (error) { // ignore (we are likely disposed and cancelled) - }); + } } private async existsChildStrictCase(path: string): Promise { if (isLinux) { - return await Promises.exists(path); + return Promises.exists(path); } try { + const pathBasename = basename(path); const children = await Promises.readdir(dirname(path)); - return children.some(child => child === basename(path)); - } catch { + + return children.some(child => child === pathBasename); + } catch (error) { + this.trace(error); + return false; } } @@ -380,6 +429,8 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche } override dispose(): void { + this.trace('stopping file watcher'); + this.cts.dispose(true); super.dispose(); diff --git a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts index c129369151591..b8e596f45a2ac 100644 --- a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts @@ -14,6 +14,7 @@ import { NodeJSFileWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsW import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { getDriveLetter } from 'vs/base/common/extpath'; import { ltrim } from 'vs/base/common/strings'; +import { DeferredPromise } from 'vs/base/common/async'; // this suite has shown flaky runs in Azure pipelines where // tasks would just hang and timeout after a while (not in @@ -23,7 +24,7 @@ import { ltrim } from 'vs/base/common/strings'; ((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('File Watcher (node.js)', () => { let testDir: string; - let watcher: NodeJSFileWatcher; + let watcher: TestNodeJSFileWatcher; let event: Event; let loggingEnabled = false; @@ -35,6 +36,18 @@ import { ltrim } from 'vs/base/common/strings'; enableLogging(false); + class TestNodeJSFileWatcher extends NodeJSFileWatcher { + + private readonly _whenDisposed = new DeferredPromise(); + readonly whenDisposed = this._whenDisposed.p; + + override dispose(): void { + super.dispose(); + + this._whenDisposed.complete(); + } + } + setup(async function () { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'filewatcher'); @@ -53,7 +66,7 @@ import { ltrim } from 'vs/base/common/strings'; const emitter = new Emitter(); event = emitter.event; - watcher = new NodeJSFileWatcher({ path, excludes: [] }, changes => emitter.fire(changes), msg => { + watcher = new TestNodeJSFileWatcher({ path, excludes: [] }, changes => emitter.fire(changes), msg => { if (loggingEnabled) { console.log(`[recursive watcher test message] ${msg.type}: ${msg.message}`); } @@ -417,10 +430,9 @@ import { ltrim } from 'vs/base/common/strings'; const watchedPath = join(testDir, 'deep'); await createWatcher(watchedPath); - // Delete watched path - const changeFuture = awaitEvent(event, watchedPath, FileChangeType.DELETED); + // Delete watched path and ensure watcher is now disposed Promises.rm(watchedPath, RimRafMode.UNLINK); - await changeFuture; + await watcher.whenDisposed; }); test('deleting watched path is handled properly (file watch)', async function () { @@ -431,5 +443,8 @@ import { ltrim } from 'vs/base/common/strings'; const changeFuture = awaitEvent(event, watchedPath, FileChangeType.DELETED); Promises.unlink(watchedPath); await changeFuture; + + // Ensure watcher is now disposed + await watcher.whenDisposed; }); }); From 898073bcdb9fb1ce50a19f0f973b745165815d54 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 5 Jan 2022 10:29:02 +0100 Subject: [PATCH 1006/2210] fix tests (watcher tests have moved) --- .../files/test/node/diskFileService.test.ts | 231 +----------------- 1 file changed, 4 insertions(+), 227 deletions(-) diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 5c71228ae1ae6..bae2ef6735b42 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { createReadStream, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'fs'; +import { createReadStream, existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs'; import { tmpdir } from 'os'; import { timeout } from 'vs/base/common/async'; import { bufferToReadable, bufferToStream, streamToBuffer, streamToBufferReadableStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; @@ -12,11 +12,11 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { basename, dirname, join, posix } from 'vs/base/common/path'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { isEqual, joinPath } from 'vs/base/common/resources'; +import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { Promises, rimrafSync } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { etag, FileAtomicReadOptions, FileChangeType, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, hasFileAtomicReadCapability, hasOpenReadWriteCloseCapability, IFileChange, IFileStat, IFileStatWithMetadata, IReadFileOptions, IStat, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; +import { etag, FileAtomicReadOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, hasFileAtomicReadCapability, hasOpenReadWriteCloseCapability, IFileStat, IFileStatWithMetadata, IReadFileOptions, IStat, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -2260,229 +2260,6 @@ flakySuite('Disk File Service', function () { assert.ok(error); }); - const runWatchTests = isLinux; - - (runWatchTests ? test : test.skip)('watch - file', async () => { - const toWatch = URI.file(join(testDir, 'index-watch1.html')); - writeFileSync(toWatch.fsPath, 'Init'); - - const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); - setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes'), 50); - await promise; - }); - - (runWatchTests && !isWindows /* windows: cannot create file symbolic link without elevated context */ ? test : test.skip)('watch - file symbolic link', async () => { - const toWatch = URI.file(join(testDir, 'lorem.txt-linked')); - await Promises.symlink(join(testDir, 'lorem.txt'), toWatch.fsPath); - - const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); - setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes'), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - file - multiple writes', async () => { - const toWatch = URI.file(join(testDir, 'index-watch1.html')); - writeFileSync(toWatch.fsPath, 'Init'); - - const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); - setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes 1'), 0); - setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes 2'), 10); - setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes 3'), 20); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - file - delete file', async () => { - const toWatch = URI.file(join(testDir, 'index-watch1.html')); - writeFileSync(toWatch.fsPath, 'Init'); - - const promise = assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]]); - setTimeout(() => unlinkSync(toWatch.fsPath), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - file - rename file', async () => { - const toWatch = URI.file(join(testDir, 'index-watch1.html')); - const toWatchRenamed = URI.file(join(testDir, 'index-watch1-renamed.html')); - writeFileSync(toWatch.fsPath, 'Init'); - - const promise = assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]]); - setTimeout(() => renameSync(toWatch.fsPath, toWatchRenamed.fsPath), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - file - rename file (different case)', async () => { - const toWatch = URI.file(join(testDir, 'index-watch1.html')); - const toWatchRenamed = URI.file(join(testDir, 'INDEX-watch1.html')); - writeFileSync(toWatch.fsPath, 'Init'); - - const promise = isLinux - ? assertWatch(toWatch, [[FileChangeType.DELETED, toWatch]]) - : assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); // case insensitive file system treat this as change - - setTimeout(() => renameSync(toWatch.fsPath, toWatchRenamed.fsPath), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - file (atomic save)', async () => { - const toWatch = URI.file(join(testDir, 'index-watch2.html')); - writeFileSync(toWatch.fsPath, 'Init'); - - const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]); - - setTimeout(() => { - // Simulate atomic save by deleting the file, creating it under different name - // and then replacing the previously deleted file with those contents - const renamed = `${toWatch.fsPath}.bak`; - unlinkSync(toWatch.fsPath); - writeFileSync(renamed, 'Changes'); - renameSync(renamed, toWatch.fsPath); - }, 50); - - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - change file', async () => { - const watchDir = URI.file(join(testDir, 'watch3')); - mkdirSync(watchDir.fsPath); - - const file = URI.file(join(watchDir.fsPath, 'index.html')); - writeFileSync(file.fsPath, 'Init'); - - const promise = assertWatch(watchDir, [[FileChangeType.UPDATED, file]]); - setTimeout(() => writeFileSync(file.fsPath, 'Changes'), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - add file', async () => { - const watchDir = URI.file(join(testDir, 'watch4')); - mkdirSync(watchDir.fsPath); - - const file = URI.file(join(watchDir.fsPath, 'index.html')); - - const promise = assertWatch(watchDir, [[FileChangeType.ADDED, file]]); - setTimeout(() => writeFileSync(file.fsPath, 'Changes'), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - delete file', async () => { - const watchDir = URI.file(join(testDir, 'watch5')); - mkdirSync(watchDir.fsPath); - - const file = URI.file(join(watchDir.fsPath, 'index.html')); - writeFileSync(file.fsPath, 'Init'); - - const promise = assertWatch(watchDir, [[FileChangeType.DELETED, file]]); - setTimeout(() => unlinkSync(file.fsPath), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - add folder', async () => { - const watchDir = URI.file(join(testDir, 'watch6')); - mkdirSync(watchDir.fsPath); - - const folder = URI.file(join(watchDir.fsPath, 'folder')); - - const promise = assertWatch(watchDir, [[FileChangeType.ADDED, folder]]); - setTimeout(() => mkdirSync(folder.fsPath), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - delete folder', async () => { - const watchDir = URI.file(join(testDir, 'watch7')); - mkdirSync(watchDir.fsPath); - - const folder = URI.file(join(watchDir.fsPath, 'folder')); - mkdirSync(folder.fsPath); - - const promise = assertWatch(watchDir, [[FileChangeType.DELETED, folder]]); - setTimeout(() => rimrafSync(folder.fsPath), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - symbolic link - change file', async () => { - const watchDir = URI.file(join(testDir, 'deep-link')); - await Promises.symlink(join(testDir, 'deep'), watchDir.fsPath, 'junction'); - - const file = URI.file(join(watchDir.fsPath, 'index.html')); - writeFileSync(file.fsPath, 'Init'); - - const promise = assertWatch(watchDir, [[FileChangeType.UPDATED, file]]); - setTimeout(() => writeFileSync(file.fsPath, 'Changes'), 50); - await promise; - }); - - (runWatchTests ? test : test.skip)('watch - folder (non recursive) - rename file', async () => { - const watchDir = URI.file(join(testDir, 'watch8')); - mkdirSync(watchDir.fsPath); - - const file = URI.file(join(watchDir.fsPath, 'index.html')); - writeFileSync(file.fsPath, 'Init'); - - const fileRenamed = URI.file(join(watchDir.fsPath, 'index-renamed.html')); - - const promise = assertWatch(watchDir, [[FileChangeType.DELETED, file], [FileChangeType.ADDED, fileRenamed]]); - setTimeout(() => renameSync(file.fsPath, fileRenamed.fsPath), 50); - await promise; - }); - - (runWatchTests && isLinux /* this test requires a case sensitive file system */ ? test : test.skip)('watch - folder (non recursive) - rename file (different case)', async () => { - const watchDir = URI.file(join(testDir, 'watch8')); - mkdirSync(watchDir.fsPath); - - const file = URI.file(join(watchDir.fsPath, 'index.html')); - writeFileSync(file.fsPath, 'Init'); - - const fileRenamed = URI.file(join(watchDir.fsPath, 'INDEX.html')); - - const promise = assertWatch(watchDir, [[FileChangeType.DELETED, file], [FileChangeType.ADDED, fileRenamed]]); - setTimeout(() => renameSync(file.fsPath, fileRenamed.fsPath), 50); - await promise; - }); - - function assertWatch(toWatch: URI, expected: [FileChangeType, URI][]): Promise { - return new Promise((resolve, reject) => { - const watcherDisposable = service.watch(toWatch); - - function toString(type: FileChangeType): string { - switch (type) { - case FileChangeType.ADDED: return 'added'; - case FileChangeType.DELETED: return 'deleted'; - case FileChangeType.UPDATED: return 'updated'; - } - } - - function printEvents(raw: readonly IFileChange[]): string { - return raw.map(change => `Change: type ${toString(change.type)} path ${change.resource.toString()}`).join('\n'); - } - - const listenerDisposable = service.onDidChangeFilesRaw(({ changes }) => { - watcherDisposable.dispose(); - listenerDisposable.dispose(); - - try { - assert.strictEqual(changes.length, expected.length, `Expected ${expected.length} events, but got ${changes.length}. Details (${printEvents(changes)})`); - - if (expected.length === 1) { - assert.strictEqual(changes[0].type, expected[0][0], `Expected ${toString(expected[0][0])} but got ${toString(changes[0].type)}. Details (${printEvents(changes)})`); - assert.strictEqual(changes[0].resource.fsPath, expected[0][1].fsPath); - } else { - for (const expect of expected) { - assert.strictEqual(hasChange(changes, expect[0], expect[1]), true, `Unable to find ${toString(expect[0])} for ${expect[1].fsPath}. Details (${printEvents(changes)})`); - } - } - - resolve(); - } catch (error) { - reject(error); - } - }); - }); - } - - function hasChange(changes: readonly IFileChange[], type: FileChangeType, resource: URI): boolean { - return changes.some(change => change.type === type && isEqual(change.resource, resource)); - } - test('read - mixed positions', async () => { const resource = URI.file(join(testDir, 'lorem.txt')); From 5e630c145f53e9a9a2975806bf8530e64ba43ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 5 Jan 2022 10:11:12 +0000 Subject: [PATCH 1007/2210] Enable IPC API for web (#138054) * wip: ipc api * wip: send message ports upfront * address both inside and outside iframe * fix build * relay MessagePort to worker * address api discussion feedback * check for proposed api * fix layer breakage Co-authored-by: Alex Dima --- build/lib/layersChecker.js | 4 +- build/lib/layersChecker.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 9 ++++- .../api/common/extHostExtensionService.ts | 12 +++++- .../browser/webWorkerExtensionHost.ts | 4 ++ .../extensions/common/extensionHostMain.ts | 5 ++- .../common/extensionsApiProposals.ts | 1 + .../extensions/worker/extensionHostWorker.ts | 32 +++++++++------- .../worker/webWorkerExtensionHostIframe.html | 4 +- src/vs/workbench/workbench.web.api.ts | 7 ++++ src/vscode-dts/vscode.proposed.ipc.d.ts | 37 +++++++++++++++++++ 11 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.ipc.d.ts diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index b5976320f607e..7f0637af940a8 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -52,7 +52,9 @@ const CORE_TYPES = [ 'trimEnd', 'trimLeft', 'trimRight', - 'queueMicrotask' + 'queueMicrotask', + 'MessageChannel', + 'MessagePort' ]; // Types that are defined in a common layer but are known to be only // available in native environments should not be allowed in browser diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index ac48d287771bd..e62394a405cbe 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -53,7 +53,9 @@ const CORE_TYPES = [ 'trimEnd', 'trimLeft', 'trimRight', - 'queueMicrotask' + 'queueMicrotask', + 'MessageChannel', + 'MessagePort' ]; // Types that are defined in a common layer but are known to be only diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index db0307e22df83..87be1511e36cb 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -98,6 +98,12 @@ export interface IWorkspaceData extends IStaticWorkspaceData { folders: { uri: UriComponents, name: string, index: number; }[]; } +export interface MessagePortLike { + postMessage(message: any, transfer?: any[]): void; + addEventListener(type: 'message', listener: (e: any) => any): void; + removeEventListener(type: 'message', listener: (e: any) => any): void; +} + export interface IInitData { version: string; commit?: string; @@ -114,6 +120,7 @@ export interface IInitData { autoStart: boolean; remote: { isRemote: boolean; authority: string | undefined; connectionData: IRemoteConnectionData | null; }; uiKind: UIKind; + messagePorts?: ReadonlyMap; } export interface IConfigurationInitData extends IConfigurationData { @@ -2260,7 +2267,7 @@ export const MainContext = { MainThreadTheming: createMainId('MainThreadTheming'), MainThreadTunnelService: createMainId('MainThreadTunnelService'), MainThreadTimeline: createMainId('MainThreadTimeline'), - MainThreadTesting: createMainId('MainThreadTesting') + MainThreadTesting: createMainId('MainThreadTesting'), }; export const ExtHostContext = { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 7d209ca81aac3..2b6b0266bb386 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -17,7 +17,7 @@ import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/co import { ActivatedExtension, EmptyExtension, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { MissingExtensionDependency, ActivationKind, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { MissingExtensionDependency, ActivationKind, checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import * as errors from 'vs/base/common/errors'; import type * as vscode from 'vscode'; @@ -424,6 +424,10 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const that = this; let extension: vscode.Extension | undefined; + const messagePort = isProposedApiEnabled(extensionDescription, 'ipc') + ? this._initData.messagePorts?.get(ExtensionIdentifier.toKey(extensionDescription.identifier)) + : undefined; + return Object.freeze({ globalState, workspaceState, @@ -449,7 +453,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme checkProposedApiEnabled(extensionDescription, 'extensionRuntime'); return that.extensionRuntime; }, - get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); } + get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); }, + messagePassingProtocol: messagePort && { + onDidReceiveMessage: Event.fromDOMEventEmitter(messagePort, 'message', e => e.data), + postMessage: messagePort.postMessage.bind(messagePort) as any + } }); }); } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 602b4bf4e801f..4814d4482f3eb 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -187,6 +187,10 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost throw barrierError; } + // Send over message ports for extension API + const messagePorts = this._environmentService.options?.messagePorts ?? new Map(); + iframe.contentWindow!.postMessage(messagePorts, '*', [...messagePorts.values()]); + port.onmessage = (event) => { const { data } = event; if (!(data instanceof ArrayBuffer)) { diff --git a/src/vs/workbench/services/extensions/common/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts index 4570e1d0ae19b..6f575189eace3 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -45,7 +45,8 @@ export class ExtensionHostMain { protocol: IMessagePassingProtocol, initData: IInitData, hostUtils: IHostUtils, - uriTransformer: IURITransformer | null + uriTransformer: IURITransformer | null, + messagePorts?: ReadonlyMap ) { this._isTerminating = false; this._hostUtils = hostUtils; @@ -56,7 +57,7 @@ export class ExtensionHostMain { // bootstrap services const services = new ServiceCollection(...getSingletonServiceDescriptors()); - services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData }); + services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData, messagePorts }); services.set(IExtHostRpcService, new ExtHostRpcService(this._rpcProtocol)); services.set(IURITransformerService, new URITransformerService(uriTransformer)); services.set(IHostUtils, hostUtils); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index ccdd7e187e0b2..ce36019da177e 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -25,6 +25,7 @@ export const allApiProposals = Object.freeze({ fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', inlayHints: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlayHints.d.ts', inlineCompletions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts', + ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', languageIcon: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageIcon.d.ts', languageStatus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatus.d.ts', notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index f85ca21d421f2..8996ce43e355e 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -216,18 +216,24 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise nativeClose(); -export function create(): void { - const res = new ExtensionWorker(); +export function create(): { onmessage: (message: any) => void } { performance.mark(`code/extHost/willConnectToRenderer`); - connectToRenderer(res.protocol).then(data => { - performance.mark(`code/extHost/didWaitForInitData`); - const extHostMain = new ExtensionHostMain( - data.protocol, - data.initData, - hostUtil, - null, - ); - - onTerminate = (reason: string) => extHostMain.terminate(reason); - }); + const res = new ExtensionWorker(); + + return { + onmessage(messagePorts: ReadonlyMap) { + connectToRenderer(res.protocol).then(data => { + performance.mark(`code/extHost/didWaitForInitData`); + const extHostMain = new ExtensionHostMain( + data.protocol, + data.initData, + hostUtil, + null, + messagePorts + ); + + onTerminate = (reason: string) => extHostMain.terminate(reason); + }); + } + }; } diff --git a/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html index ddf31332366c8..87cc732395ddb 100644 --- a/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html +++ b/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html @@ -4,7 +4,7 @@ @@ -59,6 +59,8 @@ console.error(event.message, event.error); sendError(event.error); }; + + self.onmessage = (event) => worker.postMessage(event.data, event.ports); } catch(err) { console.error(err); sendError(err); diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 3d43cb1289dfc..76b7bb00b91c4 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -540,6 +540,13 @@ interface IWorkbenchConstructionOptions { //#endregion + //#region IPC + + readonly messagePorts?: ReadonlyMap; + + //#endregion + + //#region Development options readonly developmentOptions?: IDevelopmentOptions; diff --git a/src/vscode-dts/vscode.proposed.ipc.d.ts b/src/vscode-dts/vscode.proposed.ipc.d.ts new file mode 100644 index 0000000000000..c30fc9cbdaf44 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.ipc.d.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + /** + * A message passing protocol, which enables sending and receiving messages + * between two parties. + */ + export interface MessagePassingProtocol { + + /** + * Fired when a message is received from the other party. + */ + readonly onDidReceiveMessage: Event; + + /** + * Post a message to the other party. + * + * @param message Body of the message. This must be a JSON serializable object. + * @param transfer A collection of `ArrayBuffer` instances which can be transferred + * to the other party, saving costly memory copy operations. + */ + postMessage(message: any, transfer?: ArrayBuffer[]): void; + } + + export interface ExtensionContext { + + /** + * When not `undefined`, this is an instance of {@link MessagePassingProtocol} in + * which the other party is owned by the web embedder. + */ + readonly messagePassingProtocol: MessagePassingProtocol | undefined; + } +} From 25c6c331ededf1c91aa6befd3557f3321efc0e73 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 5 Jan 2022 11:16:36 +0100 Subject: [PATCH 1008/2210] Include root for postDebugTask (#140038) Fixes #140003 --- src/vs/workbench/contrib/debug/browser/debugService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 8cc5722084218..9e7fdf7d2c883 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -652,8 +652,9 @@ export class DebugService implements IDebugService { } if (session.configuration.postDebugTask) { + const root = session.root ?? this.contextService.getWorkspace(); try { - await this.taskRunner.runTask(session.root, session.configuration.postDebugTask); + await this.taskRunner.runTask(root, session.configuration.postDebugTask); } catch (err) { this.notificationService.error(err); } From 1351d5a62e3f529faa41229e279e65517b5431d0 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 5 Jan 2022 11:26:33 +0100 Subject: [PATCH 1009/2210] Fixes #140104. --- .../unicodeHighlighter/unicodeHighlighter.ts | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index b1fdbdcc00446..a1d0b41a2cd20 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -444,16 +444,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa // text refers to a single character. const codePoint = char.codePointAt(0)!; - function formatCodePoint(codePoint: number) { - let value = `\`U+${codePoint.toString(16).padStart(4, '0')}\``; - if (!InvisibleCharacters.isInvisibleCharacter(codePoint)) { - // Don't render any control characters or any invisible characters, as they cannot be seen anyways. - value += ` "${`${renderCodePointAsInlineCode(codePoint)}`}"`; - } - return value; - } - - const codePointStr = formatCodePoint(codePoint); + const codePointStr = formatCodePointMarkdown(codePoint); let reason: string; switch (highlightInfo.reason.kind) { @@ -462,7 +453,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa 'unicodeHighlight.characterIsAmbiguous', 'The character {0} could be confused with the character {1}, which is more common in source code.', codePointStr, - formatCodePoint(highlightInfo.reason.confusableWith.codePointAt(0)!) + formatCodePointMarkdown(highlightInfo.reason.confusableWith.codePointAt(0)!) ); break; @@ -506,6 +497,19 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa } } +function codePointToHex(codePoint: number): string { + return `U+${codePoint.toString(16).padStart(4, '0')}`; +} + +function formatCodePointMarkdown(codePoint: number) { + let value = `\`${codePointToHex(codePoint)}\``; + if (!InvisibleCharacters.isInvisibleCharacter(codePoint)) { + // Don't render any control characters or any invisible characters, as they cannot be seen anyways. + value += ` "${`${renderCodePointAsInlineCode(codePoint)}`}"`; + } + return value; +} + function renderCodePointAsInlineCode(codePoint: number): string { if (codePoint === CharCode.BackTick) { return '`` ` ``'; @@ -704,9 +708,9 @@ export class ShowExcludeOptions extends EditorAction { function getExcludeCharFromBeingHighlightedLabel(codePoint: number) { if (InvisibleCharacters.isInvisibleCharacter(codePoint)) { - return nls.localize('unicodeHighlight.excludeInvisibleCharFromBeingHighlighted', 'Exclude {0} (invisible character) from being highlighted', `U+${codePoint.toString(16)}`); + return nls.localize('unicodeHighlight.excludeInvisibleCharFromBeingHighlighted', 'Exclude {0} (invisible character) from being highlighted', codePointToHex(codePoint)); } - return nls.localize('unicodeHighlight.excludeCharFromBeingHighlighted', 'Exclude {0} from being highlighted', `U+${codePoint.toString(16)} "${char}"`); + return nls.localize('unicodeHighlight.excludeCharFromBeingHighlighted', 'Exclude {0} from being highlighted', `${codePointToHex(codePoint)} "${char}"`); } const options: ExtendedOptions[] = []; From 986f163f9fcab0ad6bbff993a29914e77b397245 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Jan 2022 11:44:39 +0100 Subject: [PATCH 1010/2210] use extension linter to warn about usage of impossible proposals, https://github.com/microsoft/vscode-internalbacklog/issues/2461 --- .../extension-editing/src/extensionLinter.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index 0a66243c6d7e4..7d9d27f83da70 100644 --- a/extensions/extension-editing/src/extensionLinter.ts +++ b/extensions/extension-editing/src/extensionLinter.ts @@ -9,7 +9,7 @@ import { URL } from 'url'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); -import { parseTree, findNodeAtLocation, Node as JsonNode } from 'jsonc-parser'; +import { parseTree, findNodeAtLocation, Node as JsonNode, getNodeValue } from 'jsonc-parser'; import * as MarkdownItType from 'markdown-it'; import { languages, workspace, Disposable, TextDocument, Uri, Diagnostic, Range, DiagnosticSeverity, Position, env } from 'vscode'; @@ -17,6 +17,7 @@ import { languages, workspace, Disposable, TextDocument, Uri, Diagnostic, Range, const product = JSON.parse(fs.readFileSync(path.join(env.appRoot, 'product.json'), { encoding: 'utf-8' })); const allowedBadgeProviders: string[] = (product.extensionAllowedBadgeProviders || []).map((s: string) => s.toLowerCase()); const allowedBadgeProvidersRegex: RegExp[] = (product.extensionAllowedBadgeProvidersRegex || []).map((r: string) => new RegExp(r)); +const extensionEnabledApiProposals: Record = product.extensionEnabledApiProposals ?? {}; function isTrustedSVGSource(uri: Uri): boolean { return allowedBadgeProviders.includes(uri.authority.toLowerCase()) || allowedBadgeProvidersRegex.some(r => r.test(uri.toString())); @@ -29,6 +30,7 @@ const dataUrlsNotValid = localize('dataUrlsNotValid', "Data URLs are not a valid const relativeUrlRequiresHttpsRepository = localize('relativeUrlRequiresHttpsRepository', "Relative image URLs require a repository with HTTPS protocol to be specified in the package.json."); const relativeIconUrlRequiresHttpsRepository = localize('relativeIconUrlRequiresHttpsRepository', "An icon requires a repository with HTTPS protocol to be specified in this package.json."); const relativeBadgeUrlRequiresHttpsRepository = localize('relativeBadgeUrlRequiresHttpsRepository', "Relative badge URLs require a repository with HTTPS protocol to be specified in this package.json."); +const apiProposalNotListed = localize('apiProposalNotListed', "This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the VS Code team."); enum Context { ICON, @@ -130,6 +132,23 @@ export class ExtensionLinter { .map(url => this.addDiagnostics(diagnostics, document, url!.offset + 1, url!.offset + url!.length - 1, url!.value, Context.BADGE, info)); } + const publisher = findNodeAtLocation(tree, ['publisher']); + const name = findNodeAtLocation(tree, ['name']); + const enabledApiProposals = findNodeAtLocation(tree, ['enabledApiProposals']); + if (publisher?.type === 'string' && name?.type === 'string' && enabledApiProposals?.type === 'array') { + const extensionId = `${getNodeValue(publisher)}.${getNodeValue(name)}`; + const effectiveProposalNames = extensionEnabledApiProposals[extensionId]; + if (Array.isArray(effectiveProposalNames) && enabledApiProposals.children) { + for (const child of enabledApiProposals.children) { + if (child.type === 'string' && !effectiveProposalNames.includes(getNodeValue(child))) { + const start = document.positionAt(child.offset); + const end = document.positionAt(child.offset + child.length); + diagnostics.push(new Diagnostic(new Range(start, end), apiProposalNotListed, DiagnosticSeverity.Error)); + } + } + } + } + } this.diagnosticsCollection.set(document.uri, diagnostics); }); From ffdb8427ed1140e995ae008cc1ad0af2a157de29 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Jan 2022 11:46:05 +0100 Subject: [PATCH 1011/2210] :lipstick: use native endsWith, fyi @chrmarti --- .../extension-editing/src/extensionLinter.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index 7d9d27f83da70..d4067e5265ad8 100644 --- a/extensions/extension-editing/src/extensionLinter.ts +++ b/extensions/extension-editing/src/extensionLinter.ts @@ -77,7 +77,7 @@ export class ExtensionLinter { private queue(document: TextDocument) { const p = document.uri.path; - if (document.languageId === 'json' && endsWith(p, '/package.json')) { + if (document.languageId === 'json' && p.endsWith('/package.json')) { this.packageJsonQ.add(document); this.startTimer(); } @@ -86,7 +86,7 @@ export class ExtensionLinter { private queueReadme(document: TextDocument) { const p = document.uri.path; - if (document.languageId === 'markdown' && (endsWith(p.toLowerCase(), '/readme.md') || endsWith(p.toLowerCase(), '/changelog.md'))) { + if (document.languageId === 'markdown' && (p.toLowerCase().endsWith('/readme.md') || p.toLowerCase().endsWith('/changelog.md'))) { this.readmeQ.add(document); this.startTimer(); } @@ -348,7 +348,7 @@ export class ExtensionLinter { diagnostics.push(new Diagnostic(range, message, DiagnosticSeverity.Warning)); } - if (endsWith(uri.path.toLowerCase(), '.svg') && !isTrustedSVGSource(uri)) { + if (uri.path.toLowerCase().endsWith('.svg') && !isTrustedSVGSource(uri)) { const range = new Range(document.positionAt(begin), document.positionAt(end)); diagnostics.push(new Diagnostic(range, svgsNotValid, DiagnosticSeverity.Warning)); } @@ -365,17 +365,6 @@ export class ExtensionLinter { } } -function endsWith(haystack: string, needle: string): boolean { - let diff = haystack.length - needle.length; - if (diff > 0) { - return haystack.indexOf(needle, diff) === diff; - } else if (diff === 0) { - return haystack === needle; - } else { - return false; - } -} - function parseUri(src: string, base?: string, retry: boolean = true): Uri | null { try { let url = new URL(src, base); From 7046a368fb98b136457118a67a2b453d42ad693f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Jan 2022 15:16:42 +0100 Subject: [PATCH 1012/2210] don't accidentially stop asking for completions because snippets happened, https://github.com/microsoft/vscode/issues/139559 --- src/vs/editor/contrib/suggest/suggest.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index a58f9aa3f6156..243c3e9a623cf 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -211,9 +211,10 @@ export async function provideSuggestionItems( const durations: CompletionDurationEntry[] = []; let needsClipboard = false; - const onCompletionList = (provider: modes.CompletionItemProvider, container: modes.CompletionList | null | undefined, sw: StopWatch) => { + const onCompletionList = (provider: modes.CompletionItemProvider, container: modes.CompletionList | null | undefined, sw: StopWatch): boolean => { + let didAddResult = false; if (!container) { - return; + return didAddResult; } for (let suggestion of container.suggestions) { if (!options.kindFilter.has(suggestion.kind)) { @@ -233,6 +234,7 @@ export async function provideSuggestionItems( needsClipboard = SnippetParser.guessNeedsClipboard(suggestion.insertText); } result.push(new CompletionItem(position, suggestion, container, provider)); + didAddResult = true; } } if (isDisposable(container)) { @@ -241,6 +243,7 @@ export async function provideSuggestionItems( durations.push({ providerName: provider._debugDisplayName ?? 'unkown_provider', elapsedProvider: container.duration ?? -1, elapsedOverall: sw.elapsed() }); + return didAddResult; }; // ask for snippets in parallel to asking "real" providers. Only do something if configured to @@ -263,8 +266,7 @@ export async function provideSuggestionItems( for (let providerGroup of modes.CompletionProviderRegistry.orderedGroups(model)) { // for each support in the group ask for suggestions - let lenBefore = result.length; - + let didAddResult = false; await Promise.all(providerGroup.map(async provider => { if (options.providerFilter.size > 0 && !options.providerFilter.has(provider)) { return; @@ -272,13 +274,13 @@ export async function provideSuggestionItems( try { const sw = new StopWatch(true); const list = await provider.provideCompletionItems(model, position, context, token); - onCompletionList(provider, list, sw); + didAddResult = onCompletionList(provider, list, sw) || didAddResult; } catch (err) { onUnexpectedExternalError(err); } })); - if (lenBefore !== result.length || token.isCancellationRequested) { + if (didAddResult || token.isCancellationRequested) { break; } } From 675014ed539b6943ed78bef600d891fd3ac5fa91 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 5 Jan 2022 15:42:42 +0100 Subject: [PATCH 1013/2210] Play a sound when the cursor enters a line with a breakpoint. --- build/filters.js | 5 +- src/vs/base/common/event.ts | 17 ++++ .../electron-browser/workbench/workbench.html | 2 +- .../electron-sandbox/workbench/workbench.html | 2 +- .../audioCues/browser/audioCueContribution.ts | 92 ++++++++++++++++++ .../browser/audioCues.contribution.ts | 29 ++++++ .../browser/media/breakpointHit.webm | Bin 0 -> 11224 bytes .../audioCues/browser/media/error.webm | Bin 0 -> 10910 bytes src/vs/workbench/workbench.common.main.ts | 3 + 9 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts create mode 100644 src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts create mode 100644 src/vs/workbench/contrib/audioCues/browser/media/breakpointHit.webm create mode 100644 src/vs/workbench/contrib/audioCues/browser/media/error.webm diff --git a/build/filters.js b/build/filters.js index 872ed47f9ea09..6f746545e9b62 100644 --- a/build/filters.js +++ b/build/filters.js @@ -32,7 +32,7 @@ module.exports.unicodeFilter = [ '!LICENSES.chromium.html', '!**/LICENSE', - '!**/*.{dll,exe,png,bmp,jpg,scpt,cur,ttf,woff,eot,template,ico,icns}', + '!**/*.{dll,exe,png,bmp,jpg,scpt,cur,ttf,woff,eot,template,ico,icns,webm}', '!**/test/**', '!**/*.test.ts', '!**/*.{d.ts,json,md}', @@ -108,7 +108,7 @@ module.exports.indentationFilter = [ '!src/vs/*/**/*.d.ts', '!src/typings/**/*.d.ts', '!extensions/**/*.d.ts', - '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist}', + '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist,webm}', '!build/{lib,download,linux,darwin}/**/*.js', '!build/**/*.sh', '!build/azure-pipelines/**/*.js', @@ -133,6 +133,7 @@ module.exports.copyrightFilter = [ '!**/*.bat', '!**/*.cmd', '!**/*.ico', + '!**/*.webm', '!**/*.icns', '!**/*.xml', '!**/*.sh', diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index ecbac6597a74f..8160ace0dce39 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -371,6 +371,23 @@ export namespace Event { handler(undefined); return event(e => handler(e)); } + + export function runAndSubscribeWithStore(event: Event, handler: (e: T | undefined, disposableStore: DisposableStore) => any): IDisposable { + let store: DisposableStore | null = null; + + function run(e: T | undefined) { + store?.dispose(); + store = new DisposableStore(); + handler(e, store); + } + + run(undefined); + const disposable = event(e => run(e)); + return toDisposable(() => { + disposable.dispose(); + store?.dispose(); + }); + } } export type Listener = [(e: T) => void, any] | ((e: T) => void); diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index 0efd653996039..47066f520be18 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index 0efd653996039..47066f520be18 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts new file mode 100644 index 0000000000000..5a8184b16639e --- /dev/null +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Event } from 'vs/base/common/event'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { raceTimeout } from 'vs/base/common/async'; +import { FileAccess } from 'vs/base/common/network'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; + +export class AudioCueContribution extends DisposableStore implements IWorkbenchContribution { + constructor( + @IDebugService readonly debugService: IDebugService, + @IEditorService readonly editorService: IEditorService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + ) { + super(); + + this.add(Event.runAndSubscribeWithStore(editorService.onDidActiveEditorChange, (_, store) => { + let lastLineNumber = -1; + + const activeTextEditorControl = editorService.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + store.add( + activeTextEditorControl.onDidChangeCursorPosition(() => { + const model = activeTextEditorControl.getModel(); + if (!model) { + return; + } + const position = activeTextEditorControl.getPosition(); + if (!position) { + return; + } + const lineNumber = position.lineNumber; + if (lineNumber === lastLineNumber) { + return; + } + lastLineNumber = lineNumber; + + const uri = model.uri; + + const breakpoints = debugService.getModel().getBreakpoints({ uri, lineNumber }); + const hasBreakpoints = breakpoints.length > 0; + + if (hasBreakpoints) { + this.handleBreakpointOnLine(); + } + }) + ); + } + })); + } + + private get audioCuesEnabled(): boolean { + const value = this._configurationService.getValue<'smart' | 'on' | 'off'>('audioCues.enabled'); + if (value === 'on') { + return true; + } else if (value === 'smart') { + return this.accessibilityService.isScreenReaderOptimized(); + } else { + return false; + } + } + + public handleBreakpointOnLine(): void { + this.playSound('breakpointHit'); + } + + private async playSound(fileName: string) { + if (!this.audioCuesEnabled) { + return; + } + + const url = FileAccess.asBrowserUri(`vs/workbench/contrib/audioCues/browser/media/${fileName}.webm`, require).toString(); + const audio = new Audio(url); + + try { + // Don't play when loading takes more than 1s, due to loading, decoding or playing issues. + // Delayed sounds are very confusing. + await raceTimeout(audio.play(), 1000); + } catch (e) { + audio.remove(); + } + } +} diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts new file mode 100644 index 0000000000000..7356c411cdd87 --- /dev/null +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { AudioCueContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueContribution'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueContribution, LifecyclePhase.Eventually); + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + 'properties': { + 'audioCues.enabled': { + 'type': 'string', + 'description': localize('audioCues.enabled', "Controls whether audio cues are enabled."), + 'enum': ['smart', 'on', 'off'], + 'default': 'smart', + 'enumDescriptions': [ + localize('audioCues.enabled.smart', "Enable audio cues when a screen reader is attached."), + localize('audioCues.enabled.on', "Enable audio cues."), + localize('audioCues.enabled.off', "Disable audio cues.") + ], + } + } +}); diff --git a/src/vs/workbench/contrib/audioCues/browser/media/breakpointHit.webm b/src/vs/workbench/contrib/audioCues/browser/media/breakpointHit.webm new file mode 100644 index 0000000000000000000000000000000000000000..030f938a0091bfa4b5bf17353922615d0849cf85 GIT binary patch literal 11224 zcmcI~Wo%ttujV;nY?zrFYB*__nHy}FnX%z>!VNPsGjkedW@b*)FmtBw``x)S_wPuv zwD*!MYk5ERV_C~v#uwkp&lU*=14SPGzSm$VksC0ah<~8FsgbQn$X_uC41ucxG6w?w zS@b1n^64&Z^6lX&<$+kzrDiG>;N1U6*s4|5f2C#l_K3eabk#m{l?s2L;D1CymG-|% zfBLx7|JVV1|JU6AkL@2+b#zO$0VpCu@}NjLLpL*yuV1)0zpyYfYY6{OS?p)1kU#Uk zDE@)JTlK%uErEf~1EB!xq*S5%VxtT&fV?pyz}3zqI9OO;QAtfjy)3}^OOASbAXI+? zAOH-p6Ppc&XbcrPa`@MnkqMa8I4@q&!PQyH)X)S7@f8Sa|5x)*T!hk{o&W1YG{)T^ z^Wpyz@Wt~(#1%vp#l*jfh5S#T|9!x~xI5hV{~-JCvlRdL*`&Wt8~=5hg^m3muSsnz zjqLwIF(1wo&kq$-`zEX^t*9UtlEBQ&^Y8P8<0~ukzY72$5p8m{tT+M#@`V?`008LK z?|C=_4p{fO0Wn41q3F*8dgX6}nsyuF-E}1k4)Vhl1gW+K#XIoQ%7#+f7Znpk-_+}I57h8iK|FO7bOwO1uoCgMA z{pFSUw5s^?D~Q!{#OH7lwIBf_?7MM| zwoGcEKh~_OGnCNL!>-@h^b%Fg;wn{&tr$H~mR;H5vmab>63-!znIhz9QOZ{?uCb!% zWtyHUANF`TXp+@l??BG;Yc~0c5gYQ*nb&mq5jn-}#NEuR!z6CW!DU!}WVdaa44oc}~;ApBBSzRBejW*2nG{`1xCs!P%F=vlb?*=R13KfLNFR@f zKA3SWy*_&x)a{pyWmey&VG6vBVdn}jbj?k%W{KaN&cvC~ytv7%xveU8V|UTm=L?5| z0kojk(dgR36^AlNS|OUk64>7HJZIMJ@)+G3jySD_9*i-Q@;J8TdJ>LjS-HU5s{T3+ zYpg+F+z}QTkdjwqY}SWpJ=$1J(f{gscfa5IvjMIWm3Q&{9*Sl!l1axCjWm+BwY~CU z;DzQ&zih*#(=x0^V$^R?En4vkkx!^=#7aogC2)SE7sP*|ph*|iMm+I)B#<1_WWp&*9eGg=k8 zU(1#azbCmmtX>kkAF?3@wsLrEAO77E8d2QkeYs9GrdSedj zScZYRkc$A=&6+rN`$vxYhYr$5FV6cg*4hb=m!y%`a?u<_ zE1uPLuj@F^SPkO%(jouVvY1|nL9LDnTiUK<&oV9u9GLQ zZyQ@ij>C-FmKp%k0RD{>5KI3prvb(UM1z_+zNx9KXl6Vco20eJ1IQv2cs+1A z6Na&gUqK8~2C~D$rRXd7XFh4>g~J1ciUDIG{)_UfCx{=ZT6W3pxSymhZcH*;YvS@| z;lZx%lu?3T3FwS*w$!2~vh7rE7dj_@lHOu4>I@9{qO%RJMA6TLlVcitgw`7!@wUgd zs;v4i4iKy^-p5jwcVpr(I*8dL<_m+s0Oh}lXyhjmVK&wl3Ke*0C4rvCf(G&ZdFm_3 z=Mz57IWGO(U2sCa#T-lz{yo8M*_!^J1m*=MR1PZcSKT;l3vHZWOnVZQ{EcyrY||rv zWW;9a_pi=1QqUKx>%5B+-d#RRQ$t3^8aswzx6y=Hs_-wHUnr-2&KB5GMr5##Yp3pc zhHe=aq+kY;-taigN})7bV&;B#7ODF;II>40oT6*E_qLb+2zW8kxE=1|T(JSHV(W?k zCdiq##EGos^ZABR`^!6_5$bS=YigX7C!X^2EXc0KVOJB41P1U$$_58y8+PCO9hcmV zHN@7GMD?f88LR`HmWGI|wi$on3+q^@z?mDjHnkMkp~ksOtsvfm@^QX!*KXAMM^7Kt zyalA-XXU&^)5MEHP4WP+GCpF0msnqF#S5jc;qF@47#}t&YRYNZ`Spk~4{Z(UQBR85 zw$fk+0x|tiN5uF+=iz@mSkkn(4TwNoo04J;0KpHFcqq7ssluKU72sbAGQj{-(CbL% zh8sz3Q*Wo97~RfNZ>BW=g!PwSM5W`42OAbW6=krDmf$!Mf+tw))nK|GzkO>i|E*}OYv9B~|O9@$C~+J9dc=kX8Yo9|GG#}7+7Ay|&`3Usg9t7RcB9{71N<(OcOH642{ zHcUZ(t4$@;InUVuMk(jXij>Jw1{ONm|Kb@{giAWv9dlx;ZC>$g53j+ z!k|s*MayJR@wnZS6CcLm&20Ty_s&ryJ8;MysO7{+#>4Fn3(g1-L)2B8)Hp$>wu95P zQzUt`BMR6Ibri^1x8O4P8BhDP`8I}g0XwB|i6+9wH<$X0jMRPNPuy7q*D=f}#8W*N z%1|1jOkyQSEZ}-Ukq|j**&1f>ah}m<$ZHLon^BR;k$xg~-+T+1PN6#a#&e6H9T0dn zuF!oilO%F|K`!U5sO=$6=H(N<8&P@3gopmVcn{>kFJ!}4{)L}0+t!;e%nk;4fL;e& zZe*!!A9((R8YcUDqJfHkQUJS{SqIXvCTgJ<9;INnjs-T|zFt4PX{-vG3Uavyu-OLK zlCw;;lFua2NM`%5F!H*Q?c))ReNgvdFloI5?N&wbG~c4V#_hPUIWEle!r=63Y5Uco zk$FAo4YNDog2K|w@nkFp@m z5O~d<`m*~DdIfDU8^ueS5y*Fh44CG%a#IGBRBMN}PWh^%rONhk{rSRZU_d13wbzR& zcz)1k`#F#Xk|*sP34Q>p+=b}Zfg7eanXzXmR%0v2o?}XCr?#uUBsN}4eH$L~?2MRb z(K57-Jz<5QJBBS+B|i#!Q0!%Qyn)I@ZEdj(^I}R_UhQ{i4hogLe)ZAq+pfkUPl{{X ztYD8OX!Sy?2D$zdD`&}GSuaC4h#MHlv82Ym3u%E-!?0Ca{TJCcaMM?ls%fQ@@?ERN zY&TtNw;bqbZ8*f|g^FBc<>sT#R0Pkbc^cYXfi=1;b(k$h2Tj>XyAJ9SJ$H%LRILh{55&WhkLd&)pkms5F7-575N3t0#6aY{jjovNv@u1BwosunQ^ z8;{Mj!`>Y%wl4GIcjd@|pY{rba(7)Z>>2<-8fAt1)4|~GoN4s3cwu^$-$M{TBfTgA zblV$LSMhtL8k9E8WH`3GCOPkYtX9nDc;DwTVSH$#P<>~cA^53I*VsYoW&1@V^U2fw zQ&ErN5dwoD>_+z66}I?<(OYTtY31vLQ2_l_4TM9f9l~QXvVn#7UIl@UNW=*`QMJ;M zfa>ruG$tn|n;9Hy;Q6#Gn#)K~zECDJ^({cDD;i9kPA*BANA`iz%dHi6r!Pf#bt_PW zM5Qe}I8$~tv@ET5O8vc_bxf?dNyFzYjU4N92Pq+viZR8ph$Bj+QgQ~-t8imyxh_K!6!{e>_0!LmS5yF2urlY(H)|n<1 zk?Qny_2XC}>R8^v{>!LV`eYvfgMt$6VU9eO0@L&4k2jd+ z4@7R~d5)28mGP{>4}6p1fIo!&VcJ!IsZ}GQE(JI)ohNlr7|00lJus=4!Aeg9iVFE5 zpufP7E8lmivAP$d0;w`Z%5xPupgQc2`Vs3>sjZrh1dxf$OC$~Y*ad&f*Jk?$_e%Og zpjQ8a_$-!5_JHA#(gSJ_i@(tEdQ$ogEMA(Qd@X+ zq2S!CqH&d3>)^dIFy}en-W(K(<4=X5ooJn>i_qlh=w#IGA|*Q(7C&?)iQ@O@Kds9& z{M)1cc4L%Tm@F~~Qj1I`=-G45>l1Dtk(J(^&WH$CJV-zYS-%!76RztI`byp|#4m;U zLhoR}$iKzju{Bz0hg9bvuT&fiUmN?{SIU|`<$W+qsFlC0`RSp6*KGdq6&d`(9zQK5 zOn?x4^n3o<3gee4Dkr?ip2n56&4Gcsb~5Cy1ddLp%%>@(|RVfP05i3=ld<$ zXsleTT}C>c{Rzwu^8}vM^j6EpZ{tR?D15|Ai*AX>3}uZ|qZIM=5l%{j9XcaKm>#kb zix-2fY+2qGDf*HlA=a_x~d2S1>MkP;#`-^&U7P$8J&Jj$K#Cj-HL$Js@mg}Y4 zKlu*1YFivXVL@+eSf2FU#OF)wkrfPJv}9QtkF!X*!X&ly@dE4LM*c0`m>aB%2;%$GFe8Gz>Qe)-S6J- zfrp9p`H$JhBg0XK$XiOK5DLRJeAMzDBv1Av4@33--A6r2QZv=|&bz`MLEPamC&cVh zO|wMb|PxW_+%i5OBGeVa3mOT1$v#Yf5$QXgGf&fM(g|B>}(fWQM382^jo&X z)Fq1McKW2oi{tcy@Li%(oEH>xC93&OO8alYgA+b-r?N%M)QG?|n`0)GWyR{62ppHM zSK4TC-JX*=EnBJ8Hn#ST2NSnzrKo$DLhB@=xxy#C=%!0N)oH)GkaKsdzdxa`!L8bL z^Ouxx{cuzz4Vt6=d9^p_6oJK3`tY&P)!n=mJjiZ!GJF5kAW>=Emk!$B(FpfdY?4Nm zxN5MSTFH&|$C85+Q_*|^HfKijzUo-Zj9q5TBSa~A7R*wF=$<7%%?~mMYcFT8R4!6l zb3R|AiBhn<_Dq&mEvxdt2vjN-ZtCZ4-C4P?P{)c#?LK3AI9uN#*N+oI2R_eOVtNgs zd7KCTgP+)dDR5`22kWWtm#JaPe7}mDZ1U;|PSrV=?5~nThP1p+kLJyB@I?wWkn@F~ zO(1&|$SXo{7c$$qX9QaNm^*qGpT74O$kR6n+p1M{EgIJorWmT@Q9i?ITx-W0y=d2R z>})Um<)Z}*g#OFNLX)9LGg_t?64$|5DE=GFZ_#_?)nPDmW_Xd-%7E*iRGpuzu0`sj zFktamNi+l~9#v8aJ%m)EdA@F?TvmfQ%!wNe*0$pKQc&+DifYk}Y-1v#9(Z znoqpWDXV1%KhWwCL&|WI|DaQRhq@cFm3l}G)qbuzPzQVi+op%V1&1`Nalz0P^I)Hh35IYRmBEfGqV z6I5NLh^p+$at!4xRgv!;KDvY!FqayM%bAe<#b58_#)e!dgQ=c!mgPc8aN1X z!oXBOhrS~kQGIvEhYh-VPmZhy7QXrUUwoOS=_^)jr~I0tJv2t%@ExS%iGG(<&9HGF zOYAUYO!QDS16^gW77V{4l(V+I_2|!mCrdrGD~IS|k)PHPbg}QSXzp;iliiFp7i=`*!PJ~=+cZrltYpUmATcq{_eLH3UQmQ9<< zvqC;ndB*`Xnl@Z4QK{j7lY9dVWCXo-(jFVWUvyP1(fuf|BAwSjV_76z7d?IQ%z`f{ z!cFeS3jl*j&DsMt=0+I`5=X@&`T&6lL9+TlEFr%l@uG8wb)A}LKW%ug+hvNiE&xl32hoj43yeZg2g=qn+1kSIouk@RZlRY!}z z*44?bzz{&@MWFWHWzJMN>n#n5q4zrjX(Z8*6MPe^0)x*$C;wR60k;a)2o#5Ulfakf zlN|NB+y>jJsN6~n2P3iGY%xXh)uNW)m?x=(P4 zK!Ovl1L|647U}Y{J7E&Z4%3XQrh2M~e#BD66B*86>w(XtDIG{&d*pszf}#f7Cm93w zf8ipIe2c~ghP_m-ia6g;3ukKbeFA{^%wg;Y3sg(&HsV`kbyO5fXe@Ft%4E?|vo8}v zR4Cq3LYDE+{gntkBT!v%k}76Dz0(tv$6^+uL- zCws?=7NzMzuM6-#Of_fNzD^KW<3|WbPp$3luM20J_z z#^cfKox@y2Udu&)14RG^s)1gczPW(A$D-F(S68IlNu}o3q$ZUo3<^iW4g(s(yuIbidPlQ61fFif(2s;?pukevR0WB>?Br&2IM%PNB_XVm zzDw&iCt86$wA~>Q%LI?bWUCL&*{pjc-Caj?YPus#J08v=xbm9-CyxD;u6fp^2j%obsF#Nu+wT~uhx2X^moUFBo;gL?`G!x zgpoB%ld2=PfH>;@JXW2`?{I^fU}x@;{Zv9MwDrEzY%74uwiPQV{I+euc+kjXCspS? zR0gu56tX`(X0&);2wVF*G-e}!MfALX9toGS=~{9y3<{R#w&Un+Y^!VBevMp0M}EUM z18COi6mD0UBud3#v>>24a^FG;fj2E5YA-Mz$37*N@zhIf#_HX0np91qpH#AF|Y&Us_@>=?N(WR;t43aIv@^YG1abij8V?QrG`Y%tZ zcD7krpCsSIRRK=5Ot!r%g#@?9Rq};lz`)qQy)7d?N$1DV@u6Q;&phRly0=yKCg%!g zuMh^xS%QZpq!mF*&+kVFS6=&>HXx3&$fSR9j{vq2pbGwOqZsh=K2Zn`;<~uo_KT;x zP%^*26`v@rP#P=oRDjhtpE1&VlxgTQnXb62S&%B5F778g7@9JOWZ!T7X7PEk!5pw^ z4Tvp+wl#)0Uqek5&y(Jp&}wKBN_S!2v#=&TeV(D-A`(9OLkYV{+EStAe!e(;!M$wU zwv4|yPo6GbFR-aWh%Aax%3vz?Zy_oKy$&9n5>@JIYo));rB1CC{bbiG zCb^I=r|h_naP(f+4fz?Dr{Dkrm|X@RhqCPF`}z~)$YuhZzxvUgIij1)@g-+F^V6Hf zt=ZWVse@}^5xqsU3GxmXFS*GQYasc2(feiXEq^pCeEr>K-t53tqOifL!`a5QdyuJ^ zP?IY!<0xNS)XO;@fthetXCw^8bYFtp4kX`U4Gy`VMAqk2n@VdXkX%~U+)-f9=$6&3 zU88AiAE%13xU5L`|N2S|B|Kp*vmJ1jU$dICg%d&3`H?owpr zSk6xZX4Z%W_P#}dFyi}XRZfX_2hKjhF;@06KmAzcEsqA%JyQ3kY-mb0)xR~M*7l89 z!&A|p0rO(|N)Z<-FJ}+?12eec8>Rh`!X;6K~#jTOjV7 zms@?9#c`5xl|bk|w`q1X{#w4^AwDh(Yvwa4J|Qnz%bh_HM2M%WLT<>l=6fRf>O+xC zF=$ALNwZfZZ7_c2L|Z%CK3->UU*}G`?Gj6v6s(JSo`8Nv zxyYA|bd@zc{@@r+cT1T1nGa8?$uo+M{S|p_Qr~Z6xUl^Hv;jC^;11|D%YSkl*W{De zm=c8ogv|sTT~ZQDBcsJ2M7^k}S(d81lYyyvSZLl$mt?5e+7;-01s;n}_wtOVS13;+ zZfp2hu(I(Oagq63!#1p=@wpA*(vq@>cPk<$*h z)+n?~!z2=m1?(0%f%$*jAN=__J20)T)q=_e1n74o1t@#rdby`+WW)wP1Q`ajVqJ6V zvU(@k{f2;C!===gywmc1nfDB_ykS^K1Xw@KeJo}if+3-`;-+1as>wGX(EI4+H2>W4 zIIQVuObwj~+1aCLg;XOY;bIX{ViS>JL11Kys2#4OX0RTY#Sj0?g#uF& zZq0y-&K{bhP}T4#Iq_3rSSQXVyIihk+AcB!oEhv8>y<%mJ7OX1Kdj0}3(+4-VeVVN z?}MLo)h_hDh0#$J7%U-r=qsJmY$Nc@6$#v68aFpapRemzqrXmx5oJlZW2aC(Vyl`V zmRB52*%L;@Y#%%{3z{*Q=W^an)n~lMN^a>3-a%zv7&jIJZD>8%09Z;+_%UY?c`4f%V-leUFxT& z91G$NqSlF_Mi3PIF3XMEc#KQ%;Sr1xC@W-0(1prQyN|`wdK$1rSQJv7l=TuOQqoM! zgn?-WK8P(BcU81_O{{xigq1f39K}4zp67uS0vo!GvD{4XyOjeN-gp8e*O>$-?ORK( z7S*4qS2^QuXkMJgVk#HK*zs(%1(+IgU5%q>2Ile!$5WXlaNxw}n9EmJjvwdg4=L@) zLLm8zl7AjWJPHYqA~>ZOBXUqMNKzK9YONm(UX55=T038);g?7*Op)8fe(;5-4~5;k z&tj1ioMXWGI~o`Ipt-#?^Wi*%b_scCaEZxZgbFZ_4mo|Nnr{spv~7abnR$B;Jc6D{ zggPxLsrzR2S|YeMjZ$)Cq+h)4?>cn)sP*1%a6U09JqalQkC;#E}3YDmku@TLk{>`D7-W_Q;<*j(*R?xaoO0{C%)Zqzp-Z`l=0Lrh@N;fobV%r;o{Bb_KM)ZU=%|#n z(S@{E@rEg^Ju#Hlvz-D$6ge2|4Z6IX`NE}O2%^7pFg^e{eW~!D}<|za{2mBL!0p4YWVMT90`9XT9o|=^mT&kEMz~ z5y+)x2UXq>;SAxtRs=O+V9API*d-vTbqP(In^$xK2P*+tC z9UgLU=dFTZCLG?E;yP&Qq%?oRd*(y)r1n$+r-hDte)LjWOv+`geHwDKY*J) zX*MC%BaubAacjh^z<^Bpf(M6!?Yr-}onP_lb=bz?6%yf(ignoB{TC9-wxHH^AH6O4*Z8%Zh;=z!jFoeobqpT@5yGTlWhFY%oIQ9K za=J6uU(mN%xbYDY*@;z2;cZ0wsmzxta_-xysq>a2*mQwN`NA<^2$sKG9TyLz48_fi z+jr>4;JTR-D?iZbr0t!Va;XAY&2KOvLuJJ&kM6Nb_6 z=O>>Teh5gLQ4##C<6eL^N3C6}(O4#rBz-%h$TxUXdM!Fo0PcuX>4{&v3y!9#Xc5T4zzRFkV?IT3c~jH<=n(atl>8MCwODF#pTW%jJEE(rxwGt4 zjeaCZ;1Q1l65U`VeK9NxnSn>?Wr1&6q+k(-N~L@Q=jD4s^8~jI7UDFsyQ$$QMGRh4 zg|QFRw@JGjAHeH(^zR4&F@QkXfz^N^hyb2z>!v0QS;ZG< zj{qF)nrojk05$;V!aF$KUJM{g0MN<>=$8v(0E8R>T|ne4H!BchV*$zBKCXNHkx%xjap=@#T_Rk!?xQ3C|3~nSRU-` zC#6{x=AWWrBhk)4XbRNGqtX#0A6|wJ8p;>n0mBqb%=10nTcW7mncJjSH3>Ii?^9PT zf~N0p`{pmWvJ7L+Nu-t9XyuLsH*mFyC2oyh{mXuq1u11u7M>0&P(&V3`btzLr&aro zs}$`@_^!I!4J~ON9o&)}%l82KAn+B0jth#MilM;N{_g;(sc0KA*PK!nA*=?Acs5&) zC)0{y+d<~$CS!C4Z8oj%tO#hP0iQsU?1vyYfc!7FtG_WP-YIRhf(oaK$HG5@AG6YE zOvwS-m-)#V04@Qm-wdm7yJ4mQwi?gwo;CnZ5JX9z+TZ^!qyRMlP?gXwcI*vs31R;6 zaR~(>qaV6LD%dJ`p0m0FWvxnH0xjg47YHQ&b@R_`ZESmVJs8j&_5cQ6`2)8v!I1w8 DN0dP4 literal 0 HcmV?d00001 diff --git a/src/vs/workbench/contrib/audioCues/browser/media/error.webm b/src/vs/workbench/contrib/audioCues/browser/media/error.webm new file mode 100644 index 0000000000000000000000000000000000000000..fbaccf5b4399ab811230393e6d79f574eaf58c3a GIT binary patch literal 10910 zcmcI~1ydbevo0HVcMlRYXpo?LTGaChCfySs-FEJ$#7cXxLWZin~%&OP^@A8=RA zOjTD`_dNZq?x|WmO(?oim@5LxWHX(cWm_VZe+A)4{xnlA2j~As!d0oX`j8f7TcSR6m?}Ny%H;t7!T*TF$}Jzt z0D6tn|M-E>{MX(8kHMN`0?c%B`tKLf7e`7%16+~A3?LSfBh$Fa%&4C zyALSlg9W06VIrzZAQee@Ig!vL78ag=FD4FdHkN-E1O%DzAD8cr2VjUo&>9#5!vDqZ z6}!{+u_^nh4`yD5qNUPiCn)OaJ537px@yC&pRrXy5m)T^+YeYjCp^h)DuteJ`1s&- z^Evun95e4c2az?wWdoOj@)X}-@?h!u${@fEJ1?A`F!D8NY;=zz@4RI|`c}6&L%g>*k_R<=(PcW$(1MBAB3+(em_Ghn_U{LW)kzmdS);c*kXB~qYa$} zB>R-mnwVj1#R;6?BD&?5WGX40B+NGm4t3FN6IrXc2mXWvy7OC=Eu)f{>epj53X2>x zKU-W@RB>`Y`_!S2mNeY^C~E|_hv#rd8U7w+5rAnlUo^kl9J@{WC;bwQlwML+MD`oM z)La&V`oR!5{x8FUk_@!nR!cvLUs5$}kaR}X3$Deb8vTnn{<@7RwR^ALfp_?t0Mhv%~5^IDm zWY-DpD)5_*4m~{yI*%V9|w)zVh8=jJ!fj4EjOwUHa zIA;p)wG_C9$bS>VFtfJRTEud2ZVT19vP-$vY^W$oNg$_`OU;ETt2zC)>TX#b5hI)= z__f9MDYf(8vV?%ZhfDV-I0yw$0I!UP4Gqt9I&q+Q;hNthZ7{#tt^={?9=CgDFByMn zW3AlsU^K&KW^hzLOqLL~76rTFWBpZ;2Q{TdVFVuQZB%%6U=hJ_6$jz#LmLZCOrs9q zPt_aWNu0hezqjShGHEx%V?t_-tP)}RY6;0e?mplJXsmOu*Tdp{$q$SEJFJij&mBZk zr^yHv4l(x4lSD$s%Cus`=5=_&5ZeIA!Zd{-kvm;aP6fs4BM}6ILMQ|-fFbDoUj|o! zm_5F#*ho$|pYb&%@w%G7toJU!(v^gwJq6*tTU;oOfs`~h$-D<3^ZK5fO8(5+hRI-R z;sGg#IcM9bt%7eP^V`(fLr>1TmowxD{32s{lV--p_LS)28G8%5jGQ*+l9N29k=c?4 z04F!r)#+3UGT)9nJez4a?!^?JQkIG2&%ygM8Psu6t z`d(23LjtF?%te|)JpLYExwMF(_fMH2Q;mts^Sbt)gwFhSQ^_eFM;fdA- zTDrQB?PezW8jes)=W9IQNc))mVp@e-tyg$BsiNt`MIg<*?fngTy0Gy#vs0YE7Q*2sCJjRX$IGVfY6^#4pbv&FOqY-+xa6UpMU1?IewuZ zFKoNwtxlp23Q;tMWj223&P7pD;{RM!PE8r(}42jA}=+1$Z#p&Qmwg?Tr zMcjk>Za4l6B-66ECy@C>R=~K}^c+IE5|LXwV#>;5CTx1exCNGO-c6ex!sq7SgTuF_ z3@2ebpD}*kG!jUy)vapAFsOO46!0xihN@^qq|MvizfNH(6biQ~S(5(gBka>GU{s)1 z&u%o9^;z;u5! zjqhE~G2tv(19x|hZQOelQ0VcWMAZz6) z6jpT`r$6Z^J(ZH7DD@v~?6q2}JMA*=@C7`cA;zJjMZMaVxF%r})9~<;A&$Dpqy>2I zZ{_%71|+1DDUvdYMOQu4sOYiBwayw(_Q`=3a!~ePxhOQ#yxlEHI z3BznM^|#bvJ8S0T^X1bN`UIW%QvWB_goD{R;z=HWd)aP(N(WP>CH*|^ybyws;8mi4 zj3)#1QQq4Gs&FUYXZNh^Rh1tCTWTra6k!1*my0G#M}Uri6=t~!D%u<9fppFs>Q+%ivgI)ZJ!vl(U1@PKFk^e8 z|1EtfAvs`zh1YDOgpi3Zg89Cd?bNYb=G@s2nCZkP{dqb={t8dkt+<(`By}g+ z*~OLY`%F+n(JY&iy1jr zRfc)LOfAGLUMpE1Y*LM%zcP;E2j`hS%7sTKS~%$0?N{02JR&dLmf?&nCEs2$S949X zA0$exS(v?9Xw(JMUpEK1uJ;z+&dEr5Bf3V9(@P_!l4OKhOV`OJBand@3LeE>n@aQx zReWa;$n~(u;A9Z8^m0ZZa33kV$8kgLfSC7}aXbm)C{sJ|a6FeR1GGB4J)V6gKhgz+ zv=F?8bH0iVE64E^e22q>JJE@4w1SN7F@`aZiG6gEdA>&5=2Sz~Wu;=CJ2o(FaK%(n z3hze99_lBi7EQ;|+HDrhg=0-ga>od$0f{DWMfgO8YC)ZIE%rm2so97a5LBECIY@~Q zY@>wQd|n#bihr%+=4dhE>|kmt1i^zLqWxbcAMg$m%b?5_ncOyUhFkY7Z0^of+%vzE z#nZ>taatcT%oLseFt`QCh83@Fu%?E=Dl-WmC656q#q>q#bXJw31_|j?%I5#ZT864*`?+a2A zIQ3EzlqM8y{d9@%X5PLd6TCIYPphK0ik5qj*WM1 zRWDZJ3&&e>j0bDli55yVeZob`LCl^jgK~|9AQ&)2{zoEZlA#}%U-bR-K6O?V7OzG1*349cNa z<}ZJhLbF}5JJlQU-sQU~eYiC0_!*ZMNu`baMe4rN2S_0X!-fhIqut^;o$s{LtHiny zv#;rNIixmwEtrNE-yS#>;cAqz7(%#Vt4sgOvF3pqp^#OeU7MHw=J#OiQ-obOUVHqZ zHPki&0@rxW*|GcVa4OWZngp0-JdJ$+hxg2vs}V!)_bRyYdmPBWkAZTdzZ-)Qc@HP; zb*=NsPl@74eD9VfT^k8kBrE=`%N~)if0-Ib#3AA-1Yv<8>OXkse7Bk~VqzMad0N42 zOr08N_SHf@X88In;K3T4&6KuWAq|;)aCUKJLQ(`70cgyCmbV#hUACEy|u_Xrh8XlF=)+OJ8=EdUCL;7xN0K8HHRD^DPUM7qfU)(TA>5wICSqS&PDUdhe-wE1rl&m zjEIedchdH0yjqCn?v>8=mk4KC{H*Mcz%06b#izNi7~+xD&91V`Yr4QOYdUvHSRHZB z)d|_1IEH4s}SA-rgF;CUWkuiGzPc0 zw};-jR;maYJ3G53l+K*nU^XM(1gJ?dEe;$A*rz+B_{Xz1*8#QlkuIW)nNZ{yq0Qh; z%Mzir)!6RHN?fjX7BY4d{n%L;6NGEzqzvGSn?jz9nw&OUs3X?Z9)%p>;^zF+ynIum z8R7ai4#O&n`GGCsK*CGFRTiSN>F@4Nu92#{{0B>nV2HJk+{qbNbX|QwI{{Ap(W#M? zKxbX4G*zAJJ%QU!9M6ibi53;JH3z>lujhu2<$Uu<`W^pZrpzi0T4x?6X&AU9`D zs)b(?@k%_DSJVN`v8+Z~fh^VlcL4>ev>eWpZ=k>JZ`y5RrEn9K1(X| zBx$2(Dp&j+A~4*~*inYZkwW(EU%IaT{rY}dm(F2N0mJ8V6k8`{x=a|KUn2TKZ)pa( z!f2_xe~bEMHrcn03-FQd7i-*uO(AlBaFS}h@#^}sc^C9~?^&TIp1vK~E1{EarISfa zot7J4nx6gJVixwXh2L}vStpDPlMEh{f#JCs_%3S(`{hHoxWwFI~KGLH^9lDznf zmcNVoGKi<71(_h!Mg)VMbqq)F6ZKC)h(i30&(R(O-BPvz;Y$aCVl0m5i@9P$K%L^g zgbdzLzfVdCJNAGizQQ7-czf|ry8OM)NI`1xA_CZyDb;@<6%=v^7Dw*0?E|Mx*CayH z4kJ&aknz}Zy_kk{)rQb4TgPXy;v#=Mx$s=U6aAGvb)IBK?oK~Ss=gD(-!()D_NG6W z_hyN{RQBR71j&K{Fdx~~g&(G&sff%C^h**mPjjwZeQMpWlgIH#cJ2DaOyzCLQ6T)$iq7K4`g{_q%vM4md%@ zZRE~vLNOI?pZunURWmmd3Sk@UUaWUAmgB34;DFIzg_OmJlJ{KE*xaAkzwZ_2|0$`l z^@ci3gTiZ{p#W>&{JRaNFS5LQY`bGn6ZWlI7H7ycy`cn3ge_!!P(ybf)#)Li0Yhof z+*{}IdG|%GF4jqLzNOX6Ww^#|IMJvGbbpPERzk6K1l@O7rfy~i r~8G|CDbm5_O zq_7uPSNhKA3cX)0G6zfB%Eh|H=&e4qKI*3>>K$dh zzGwkOFx;l^g2r`3KUOIzl7sJLKLLu;hN=YzahNKxy#rD8|zfRY%c%xs&8w` z@jUTKU(a5!A@BqkRpxB=6pX*X#1&&vuwgBh`WDpySL(ljlt&g8{w&48evMF=a_^dY za=-MPv+I?QbdSibQuuVjWMwxvI(^KNzx}q*Gtz|7ut?WlhABIvNFqcJd#RgEytTY9 zsJH3r6}&-hWSwdK%TESL8)BGx(qY@L+lv;PP-%y+5TpzSeEBF`hFi;7*(|z_?7;?Z4rgm_PvvFJ%SIHC#~8|^wyXRkSQ%s<*X0{pC-Hir zWali#SDlp0+DExy_NN}Ya;uRZlfF7SLqTNze)2O76^H%jQc`wJWW5yNx_+-W&Sesc z{g;tb`k1!KlwcvKcBNqzD2aSWQ96kxNtf!!9%HBAq0{*_R$2M?tF{u26W;{BC*4e$ ze6mE}zmq8#l5JhS#luK}H&z2QoJX|x#NPTHV=}8j`I+)`94zn7SKQO#n=#qiM7|F0 zy6u*EK9{sz4Bc}zw+HVE}S_NrRgZsd^p08 zDy^dOCRhrxS4E(UUY`{Qr7C5bRJ`W_pL)JZHN)gvI8_ zx6Kh~bvxOm1$J>)cOoz!P#eJZuHeZs-p*CRQPSO8=3?L8F#CPF3b$~cYl?;ry9i)x z$ATAR0#aC-(r$%XXk%G)8f>z9TfRfh>Wiy6dt~7@U*Gq>KJce^BW32CPhK^?97h^UB$TWh#@nS_T>hEbpK0VH%Dzy%% z`TmJ*z=4I1_14Uxi$H$3Cn363?}d`B2shKMsx!9lJw}S9Av?z0^tH1n!@cp>ul5vl zNtAE+{M~8rme1@e1gd_lQxqG%u2LHE!pu_36hu;N1^53@l^F1^-Mb64f@aNCx)tM`3HBi`1cf z1I8J3*K*H96*a~^G-g$>Ee}cGx5)pj(Y#-+kM{fuV>LK3i|pFopMi69%K%q|aLdXhm2A0YFDr0ZxMiQ?&O2RH^kmvTigiAott7TV|Cx${N0w#MhD&Cy&?E!tfc0e z(GbRqxoFV9ZZH%+bg1d%;&l$0A%H6T4VgF=u77~u0!_h!o8z!cfL zs-H@8Sd64n!_mo!BLU=&0`lMtvAM5Hl|9Buza7p~5q#}`iEqO&`{5xXfH0vqOcyK= zOfL*2qwnMPhCja^+Q0E-*5jqMwJGI%-w=h|%OLYBPPaQW3Z2_+H+T(Xi8~Hs5{Slu zPSlj7x3*z$9nMORx)+&3Q=LJ5UEGMxr7#I#;LxepaSugEoeL;IC_6L?e4wLCg=tcO62|YWFpMlCwkxA? zZMG1V@#}mNc$QJvkROXNTF<8D>z85(n{`P>1}|mRK!-b%v)n^)5vMnU2XVv(bair z=3fpAbLn|o)OVgvxzg+HjcTffPt$3|51FI`IjJ0Y?nGYBeu9}gGS2|`nU+9T;&E+! z1B>6BJiYW)rhId_v(+5tc-|et z%oajzMMYHx{R!TEJ)!jBn?zR^SnQ!sa8;)_h6LFby(%=hs3^f+oVx%zlL& zj7U=bh|OYSneS_Y(O%?k-5Gm6KP=yj(mDyt(s35;*I8H3KC^p3;NrX)RNFEySCh2r zuwXdHp-?it<{xYlFMg-|k@^fdx4qX=kH0OVH5Z0!C0(pBqUEATm}pc8Is^lX{a%koM4P$D888ruDZI0hcGeh@2M2-?dT>Tr|yR1%0_+DK`OSB0s1DG zPPqU4n}H!o4MQ((sBOnXb}9c^u2fup0Cu`Lks8NfZtoWpw>nF&OJEO%G}wF7mOc0-jDi0w>X#x|PR9eZ%C%Egr_&-c&THss{lDa!uAKRFB6? zadSp*+Zg8iA;Cdo3qp1r(v8c$m2a@F`$Wq*mdmY3Dx={8ZyS=O)Wj`k83@kLSRyZR z_OR|)-rA?HJ8$gl1&x(qKvvQzUnzluazlN{#XML9PA|JhH0|D6knEM_wfy-|1gJp0ruD`{`np0y5zw@=i4}aTA^| z0}3aR506l$_PB$H)R^MsbH;VIp~)E113TTgxNayGgmt`*Z6WOINMiBvUL5*k2Ch(l zo`WL4xCX1?)O3`b#5>FZbtD}Wsl-fZOWiiUdHWw;^{DX^rZy03GeL53m6zc^H)rJl z48W0V@P&83&lh#sb}glzY}G2hBH!|P9A5M9AB&e&HtaOM^4<$`P~9DS(eOyD)l8%6 z{^iZIH1K_2iGRCnt8Dw{_zr0L*y^ZxjEJ4ZI+Q;@S`U5*ne8J?4fy-qL*b_RkR(w^ zlmGlmqeAucc}nxS24P#HQdy-q-d+m0pif zw?r@F;8HeQZOsd7_Xenp$wXAOl5GM04uSl!qXlx>@Ft?{jGDMf!pR@1uui&Co3RF^ zCRCFd9n)>LUC-x7HejU}l`NtLCjtLjB*TBHt5y)(@r!nJtGXVH;%cvQ#~GR|^H(2{ zF|JXVC!J+IM-zEoaVdDGXUbpVq>JQKgm$tXheypF{P51hC||Nm|MG%{YvX>5`ffN} z@(pp5F18-PJx1ATY1!a(hK-;nhHNpt3Xr(kHEiLWVmAu#G|N_R((IvgQ}DpH6oS;j zfU%DQR(qCeCgYZ5#oAA+_BtN^sVCR-ldQNADs7!(2)gx#0M%e8io#iW#8Uach)x?2 zN!713hzY-6GWgD)bQQ}M{Dl#r3=atl8P*NRw$Cdx%QN}#zSF55zyVFDuwSOXml2_4 ztGb>yEOCkBQz!AM4$vHBOn3c6SgA7=A|ws1hU(rzWNOMyD`FI*X`nMp`OY$4%fpzb zsQEVnQHv|3?HSl4q+qr6W9vN~45JjA^z~xD?pW#{`R{Rg5gO9nKq}=` z#%N|5mB5+h2Pf_Ylz|7WMf^Pl{iRG9?8|PcfTDp>M->`9tCXGkCGHqR{rvb;7}R(+ zsh%H72?#*P$_f1Ao|J33>C3G_l*mKIbY|DPFwtHm=#oxO!6A+tMN%|!c7_{7w<~_IN~ahVB7PrfEwQ3)DS~+r`3a}u%B~Zl`kKSN(Of*U3jYQ za>S`%1_7Z_tbZi6UqEi?o&(eh{;|DFuFLf|I-zCwNe4k0np-}izq7+M@quN@ctJLk z*>iqGzx1O~pb#HDF-5|Xa$BP~;^EORB*tT2{IZ-+MFckNrjg+HfamS5^8{@?UdZ|R z;W^E{&!0S5KI2Uztd4IZwUGwJ?wsrd%!* zhflG*Usc2Mm1uUI$IW+qbGsMZBbmZxw#A2Nn|Z>Ie*^?dllL7k@ zl~Kq3JSGN6c^rlk=;)! zorsaYWHuGs4Uh52(OeCbh>#yII%mWqa@m!JS&#<45BYRw4(8r$`um(jhS5P%;u)cS zaqyPh)Z4-iwdHn_4KGw@pWX25f7$a9bduw6f0vQbC-;f2-d#&6fDuh=+eyd2^FOq7 zlK5;>(i(+PUu&Eu!JZ|NF=`~?Y)BW>@bL5|gd%6xri8+@v_7rD=RN^mx+$+$rl1Y4 ze5FLSdzB9Fz4v$`BmIl19g4V{DO~o5*az*2ahy*+R_E-?8Q6g5wen>9Fx!#aHZOX)wS_aA!EV0LiRSr&W8 zD{@0w0KOgScfRmW*8kHcKLJA`_`f8}sX|^&W-aMl7@+kzy|$WEx}-w$LKitL|Vgo^}#joaiNN3uFlvT#Kda85L-KkIX`=4=!2bxK<0>(+T>S+LP0n!Cmsy z8)Lq+jYPBk(BB)?N|^Jr_oT#C&vLH_+7t7yCcU2NJ!^K6%kk}%i^A;pC|5CpOGa;I z{+WYd*3houz|!p19o|B0#u>Os??<&6=VQE$)!YVBiY;R?+MY#_sqr1X;_VWgm}nbe za@~BL@QnX6tpKAeTn8wA6FYh@a&29>{WnJC2)&|%ZyP(Y>j*l%$X`=cR%@DUms-yJ z$^A&XouL(XI2tX>do56CJv(^{T0aj3l>OO`uc(VR#w+wzmaGlY{@zR%OPn-?WQi48 zG4^RjrV9&WG)juVh{U1Tm*a;Hb*9kZpo6s2u7*I%BXdfu#q@2*RZf}!vMo3&+gWmB z8#Av2VgLD-{hn$bcV?(p<8FHrVliVi(%>D__ZT9#S^<)sYp5(M--OTOQJr>GMx#Jj zHkNTwOg)&>l;J;rfJzMC!j z7kOE6;FwaJO(m=korIPQ#+a$7{UNDw0~@EhGw>zTPtoLuv=|tP`<6+UQH>B2tz1^0i5e?nMzmv0o=L(iL>dZO(F#rw+w003^2j1 zR`&|Kbu8R*ETXMYY$$aBWiH>E2fxKTAaOq1pPqFN5W>^?Njw03$3H@Ze$oa|HGsWAO*IzWfu2eGm&e5O*+XLE6`|bTl@6wHHoETiB<>i`(7?Ac zG7z4UsOv&2-tNRtvg5A4+a@meiGCsYi)e8;L~kMJ7Z{S&|D|KbFLB{VwlHVl>P79$ zeW*%TQ8gi@xPz-0HhxC96-3#NZ?15-tnfCUMAY7KPPhs&0ap? zEAKINaY$(5vCYHEFML4by46O@Z<+A0dJ6azfebC09Sc5%^Zcy>@uZe7Q-f*!1jq}X zy^^Ti_f4`j)KP}SDBJ6Ug%EWv12aoL&P75ZsFgR`zw|EC_lgm6XqP;Lwl$`Q1!${6 zb8?e@7P=*RW3Ukb$DiffF7YZUY75BMlLPa0qhYN#@|%YfOIHiQ;6}wRP+Mrp<45k3 zk_^|;;3z|K6b0yCjVLG%%o6)8zxe5LjmiXR8g${oo7{B%k(q5!=}iM{0t9}(DnoGk z15RL=E@Tf3Vi6-|Xe#`7=K?UG;#Dy>9kC2xCn1)3eOfsbAyD!lma&_L=GyHc${+wL z4$b0}Q&1`J#J|(Jtlr=K-qD-@lPJVuV?}V!oN#w7Wlm5 zz>uO4FC80%o8)jpCcw7v;30l!pX}koPRpaYDP5c?dbi;sYX4R&(vvM##i!7#AOflO zC^E5V>#Z)W=z&>1Uqy5S{%^39tZ*Ei-OtH3kw-=w*iGXtR&o-oa|rbI)U!wK z-b}EikH~rXZ!@6Pg7O!GMaEDnG5ZH%T2vjhK|4etv2i}kf_zf_-lyIUbtt=ee!YV0 zQS>*A!B5ushJ1>FfBZ=tq=kjX=17T2l>$<9Zk>*h)<^^h{6VP20sR`2>11VXM2)eR z!GrO;lh$watkSFLdcAJF{$x7VJJ}Q*w@=ZUv6TG_@J8e!d>-l4=IP13R*BW*D7K~P zgXt(Vc3u|Az3)w=gvJ2uMoU{C=z}@!Vn+jb0Bq9Ne`-z1P{}Z3ddS9if{YYkmqV_6 zi7Txa&Kl~E1_3qqmc5<{K?(s^7ofRv=8uvGsH1aAER!bx*cj65tA)Jp)$$$h@9)=2 z50HROm>P*i+(EJxDiieiGPP@p_YjwYAOLCW%|8>O@h!2nV2Fl@k4e<40Kn!Y80x Date: Wed, 5 Jan 2022 16:48:33 +0100 Subject: [PATCH 1014/2210] only triage open issues... --- .vscode/notebooks/my-work.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 21508a934774e..1d40f14308224 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -82,7 +82,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode assignee:@me label:triage-needed" + "value": "repo:microsoft/vscode is:open assignee:@me label:triage-needed" }, { "kind": 1, From e3b2098f06ff3bc3291234f939494857cea12947 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 5 Jan 2022 09:11:19 -0800 Subject: [PATCH 1015/2210] fixes #140164 --- src/vs/workbench/browser/layout.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a9fa1ef7a3519..9e2dab4dede6b 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -276,11 +276,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // The menu bar toggles the title bar in web because it does not need to be shown for window controls only if (isWeb && menuBarVisibility === 'toggle') { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); } // The menu bar toggles the title bar in full screen for toggle and classic settings else if (this.windowState.runtime.fullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); } // Move layout call to any time the menubar @@ -312,7 +312,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService) === 'custom') { // Propagate to grid - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); this.updateWindowBorder(true); } From 44452718b8d978f2e37d2e41dadb348fc0b30139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 5 Jan 2022 17:31:08 +0000 Subject: [PATCH 1016/2210] Web: publish web files.txt (#140163) * build: publish web files.txt * fix cdn --- build/azure-pipelines/upload-cdn.js | 43 ++++++++++++++++++--------- build/azure-pipelines/upload-cdn.ts | 45 ++++++++++++++++++++--------- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index fe3817c91836d..9d2c3a47ae1d5 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const path = require("path"); const es = require("event-stream"); +const Vinyl = require("vinyl"); const vfs = require("vinyl-fs"); const util = require("../lib/util"); const filter = require("gulp-filter"); @@ -15,25 +16,41 @@ const azure = require('gulp-azure-storage'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); -function main() { - return new Promise((c, e) => { +async function main() { + const files = []; + const options = { + account: process.env.AZURE_STORAGE_ACCOUNT, + credential, + container: process.env.VSCODE_QUALITY, + prefix: commit + '/', + contentSettings: { + contentEncoding: 'gzip', + cacheControl: 'max-age=31536000, public' + } + }; + await new Promise((c, e) => { vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) .pipe(filter(f => !f.isDirectory())) .pipe(gzip({ append: false })) .pipe(es.through(function (data) { - console.log('Uploading CDN file:', data.relative); // debug + console.log('Uploading:', data.relative); // debug + files.push(data.relative); this.emit('data', data); })) - .pipe(azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: process.env.VSCODE_QUALITY, - prefix: commit + '/', - contentSettings: { - contentEncoding: 'gzip', - cacheControl: 'max-age=31536000, public' - } - })) + .pipe(azure.upload(options)) + .on('end', () => c()) + .on('error', (err) => e(err)); + }); + await new Promise((c, e) => { + const listing = new Vinyl({ + path: 'files.txt', + contents: Buffer.from(files.join('\n')), + stat: { mode: 0o666 } + }); + console.log(`Uploading: files.txt (${files.length} files)`); // debug + es.readArray([listing]) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options)) .on('end', () => c()) .on('error', (err) => e(err)); }); diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index c35582017d765..c6180507991c1 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -19,25 +19,44 @@ const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); -function main(): Promise { - return new Promise((c, e) => { +async function main(): Promise { + const files: string[] = []; + const options = { + account: process.env.AZURE_STORAGE_ACCOUNT, + credential, + container: process.env.VSCODE_QUALITY, + prefix: commit + '/', + contentSettings: { + contentEncoding: 'gzip', + cacheControl: 'max-age=31536000, public' + } + }; + + await new Promise((c, e) => { vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) .pipe(filter(f => !f.isDirectory())) .pipe(gzip({ append: false })) .pipe(es.through(function (data: Vinyl) { - console.log('Uploading CDN file:', data.relative); // debug + console.log('Uploading:', data.relative); // debug + files.push(data.relative); this.emit('data', data); })) - .pipe(azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: process.env.VSCODE_QUALITY, - prefix: commit + '/', - contentSettings: { - contentEncoding: 'gzip', - cacheControl: 'max-age=31536000, public' - } - })) + .pipe(azure.upload(options)) + .on('end', () => c()) + .on('error', (err: any) => e(err)); + }); + + await new Promise((c, e) => { + const listing = new Vinyl({ + path: 'files.txt', + contents: Buffer.from(files.join('\n')), + stat: { mode: 0o666 } as any + }); + + console.log(`Uploading: files.txt (${files.length} files)`); // debug + es.readArray([listing]) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options)) .on('end', () => c()) .on('error', (err: any) => e(err)); }); From 2d1c94f570dfdec50d12585d6e21830bd665056a Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 5 Jan 2022 09:42:45 -0800 Subject: [PATCH 1017/2210] design mode. --- .../notebook/browser/view/renderers/webviewPreloads.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 2ea5097fa6250..e862933add921 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -553,6 +553,7 @@ async function webviewPreloads(ctx: PreloadContext) { let sel = window.getSelection(); sel?.removeAllRanges(); sel?.addRange(range); + document.designMode = 'On'; while (find && matches.length < 200) { find = (window as any).find(query, /* caseSensitive*/ false, @@ -575,10 +576,8 @@ async function webviewPreloads(ctx: PreloadContext) { const lastEl: any = matches.length ? matches[matches.length - 1] : null; if (lastEl && lastEl.container.contains(anchorNode)) { - document.designMode = 'On'; // document.execCommand('hiliteColor', false, '#ff0000'); window.document.execCommand('hiliteColor', false, matchColor); - document.designMode = 'Off'; const range = window.getSelection()!.getRangeAt(0).cloneRange(); matches.push({ @@ -606,10 +605,8 @@ async function webviewPreloads(ctx: PreloadContext) { // inside output const cellId = node.parentElement?.parentElement?.id; if (cellId) { - document.designMode = 'On'; // document.execCommand('hiliteColor', false, '#ff0000'); window.document.execCommand('hiliteColor', false, matchColor); - document.designMode = 'Off'; const range = window.getSelection()!.getRangeAt(0); @@ -644,6 +641,8 @@ async function webviewPreloads(ctx: PreloadContext) { } } + document.designMode = 'Off'; + _findingMatches = matches; postNotebookMessage('didFind', { matches: matches.map((match, index) => ({ From 55074185adcd1dac293c5e8592fdf3ce0323d1f1 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 5 Jan 2022 09:48:16 -0800 Subject: [PATCH 1018/2210] fixes #140175 --- src/vs/workbench/browser/layout.ts | 31 ++++++++++++++++++------- src/vs/workbench/browser/layoutState.ts | 7 +----- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 9e2dab4dede6b..6be4016512a52 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -15,7 +15,7 @@ import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -1214,14 +1214,29 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })); } - this._register(this.storageService.onWillSaveState(() => { - // save all serializable state - this.saveState(true, true); - })); - } + this._register(this.storageService.onWillSaveState(willSaveState => { + if (willSaveState.reason === WillSaveStateReason.SHUTDOWN) { + // Side Bar Size + const sideBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) + ? this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) + : this.workbenchGrid.getViewSize(this.sideBarPartView).width; + this.stateModel.setInitializationValue(LayoutStateKeys.SIDEBAR_SIZE, sideBarSize as number); + + // Panel Size + const panelSize = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) + ? this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) + : (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) === Position.BOTTOM ? this.workbenchGrid.getViewSize(this.panelPartView).height : this.workbenchGrid.getViewSize(this.panelPartView).width); + this.stateModel.setInitializationValue(LayoutStateKeys.PANEL_SIZE, panelSize as number); - private saveState(global: boolean, workspace: boolean) { - // implement save state + // Auxiliary Bar Size + const auxiliaryBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) + ? this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) + : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; + this.stateModel.setInitializationValue(LayoutStateKeys.AUXILIARYBAR_SIZE, auxiliaryBarSize as number); + + this.stateModel.save(true, true); + } + })); } private getClientArea(): Dimension { diff --git a/src/vs/workbench/browser/layoutState.ts b/src/vs/workbench/browser/layoutState.ts index 975c02a830935..4915cee32177c 100644 --- a/src/vs/workbench/browser/layoutState.ts +++ b/src/vs/workbench/browser/layoutState.ts @@ -7,7 +7,7 @@ import { getClientArea } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { PanelAlignment, Position, positionFromString, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; interface IWorkbenchLayoutStateKey { @@ -94,11 +94,6 @@ export class LayoutStateModel extends Disposable { private readonly container: HTMLElement) { super(); this.configurationService.onDidChangeConfiguration(configurationChange => this.updateStateFromLegacySettings(configurationChange)); - this.storageService.onWillSaveState(willSaveState => { - if (willSaveState.reason === WillSaveStateReason.SHUTDOWN) { - this.save(true, true); - } - }); } private updateStateFromLegacySettings(configurationChangeEvent: IConfigurationChangeEvent): void { From b45dd3244c5532c45b394f5d767edf0a36a5af2b Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 5 Jan 2022 11:00:26 -0800 Subject: [PATCH 1019/2210] Update "Learn" SVG - Fixes #140159 --- .../gettingStarted/common/media/learn.svg | 180 ++++++++---------- 1 file changed, 84 insertions(+), 96 deletions(-) diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/learn.svg b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/learn.svg index 23417ed8b8845..af1735e2d14c4 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/learn.svg +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/learn.svg @@ -1,97 +1,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 10ed57042f5d4f381d75f6bcc349fe5192cb79de Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 5 Jan 2022 11:16:44 -0800 Subject: [PATCH 1020/2210] Add context key for editor tab visibility (#139336) * Add context key for editor tab visibility Rather than reading from `'config.workbench.editor.showTabs'`, which does not reflect tabs being hidden due to zen mode. Fixes #139303 * Move context key management logic up * Better handle undefined case * Ensure key is set on startup * :lipstick: Co-authored-by: Benjamin Pasero --- src/vs/workbench/browser/contextkeys.ts | 17 ++++++++++++++--- .../browser/parts/editor/editor.contribution.ts | 14 +++++++------- src/vs/workbench/common/editor.ts | 3 ++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index a6b1368a61d6a..97ca960b03e3a 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, EditorInputCapabilities, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, EditorInputCapabilities, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, WebFileSystemAccess } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -86,6 +86,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private panelVisibleContext: IContextKey; private panelMaximizedContext: IContextKey; private auxiliaryBarVisibleContext: IContextKey; + private editorTabsVisibleContext: IContextKey; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -191,6 +192,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Editor Area this.editorAreaVisibleContext = EditorAreaVisibleContext.bindTo(this.contextKeyService); + this.editorTabsVisibleContext = EditorTabsVisibleContext.bindTo(this.contextKeyService); // Sidebar this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService); @@ -203,7 +205,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this.panelMaximizedContext = PanelMaximizedContext.bindTo(this.contextKeyService); this.panelMaximizedContext.set(this.layoutService.isPanelMaximized()); - // Auxiliarybar + // Auxiliary Bar this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService); this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART)); @@ -211,7 +213,10 @@ export class WorkbenchContextKeysHandler extends Disposable { } private registerListeners(): void { - this.editorGroupService.whenReady.then(() => this.updateEditorContextKeys()); + this.editorGroupService.whenReady.then(() => { + this.updateEditorAreaContextKeys(); + this.updateEditorContextKeys(); + }); this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); @@ -223,6 +228,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.editorGroupService.onDidChangeActiveGroup(() => this.updateEditorGroupContextKeys())); this._register(this.editorGroupService.onDidChangeGroupLocked(() => this.updateEditorGroupContextKeys())); + this._register(this.editorGroupService.onDidChangeEditorPartOptions(() => this.updateEditorAreaContextKeys())); + this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey())); @@ -255,6 +262,10 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.dirtyWorkingCopiesContext.set(workingCopy.isDirty() || this.workingCopyService.hasDirty))); } + private updateEditorAreaContextKeys(): void { + this.editorTabsVisibleContext.set(!!this.editorGroupService.partOptions.showTabs); + } + private updateEditorContextKeys(): void { const activeEditorPane = this.editorService.activeEditorPane; const visibleEditorPanes = this.editorService.visibleEditorPanes; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 1317892c14e09..95421e81731a5 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -8,8 +8,8 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { - IEditorFactoryRegistry, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorExtensions, EditorGroupEditorsCountContext, - ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorGroupEmptyContext + IEditorFactoryRegistry, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorExtensions, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, + MultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorGroupEmptyContext, EditorTabsVisibleContext } from 'vs/workbench/common/editor'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -317,7 +317,7 @@ MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: CLO // Editor Title Context Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize('close', "Close") }, group: '1_close', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeOthers', "Close Others"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: localize('closeRight', "Close to the Right"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: localize('closeRight', "Close to the Right"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 30, when: EditorTabsVisibleContext }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeAll', "Close All") }, group: '1_close', order: 50 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: REOPEN_WITH_COMMAND_ID, title: localize('reopenWith', "Reopen Editor With...") }, group: '1_open', order: 10, when: ActiveEditorAvailableEditorIdsContext }); @@ -417,7 +417,7 @@ appendEditorToolItem( title: localize('close', "Close"), icon: Codicon.close }, - ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext.toNegated()), + ContextKeyExpr.and(EditorTabsVisibleContext.toNegated(), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext.toNegated()), CLOSE_ORDER, { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, @@ -433,7 +433,7 @@ appendEditorToolItem( title: localize('close', "Close"), icon: Codicon.closeDirty }, - ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext, ActiveEditorStickyContext.toNegated()), + ContextKeyExpr.and(EditorTabsVisibleContext.toNegated(), ActiveEditorDirtyContext, ActiveEditorStickyContext.toNegated()), CLOSE_ORDER, { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, @@ -449,7 +449,7 @@ appendEditorToolItem( title: localize('unpin', "Unpin"), icon: Codicon.pinned }, - ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext), + ContextKeyExpr.and(EditorTabsVisibleContext.toNegated(), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext), CLOSE_ORDER, { id: CLOSE_EDITOR_COMMAND_ID, @@ -465,7 +465,7 @@ appendEditorToolItem( title: localize('unpin', "Unpin"), icon: Codicon.pinnedDirty }, - ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext, ActiveEditorStickyContext), + ContextKeyExpr.and(EditorTabsVisibleContext.toNegated(), ActiveEditorDirtyContext, ActiveEditorStickyContext), CLOSE_ORDER, { id: CLOSE_EDITOR_COMMAND_ID, diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index fd1b22a7c1a6d..1ba69ebd26292 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -67,6 +67,7 @@ export const InEditorZenModeContext = new RawContextKey('inZenMode', fa export const IsCenteredLayoutContext = new RawContextKey('isCenteredLayout', false, localize('isCenteredLayout', "Whether centered layout is enabled")); export const SplitEditorsVertically = new RawContextKey('splitEditorsVertically', false, localize('splitEditorsVertically', "Whether editors split vertically")); export const EditorAreaVisibleContext = new RawContextKey('editorAreaVisible', true, localize('editorAreaVisible', "Whether the editor area is visible")); +export const EditorTabsVisibleContext = new RawContextKey('editorTabsVisible', true, localize('editorTabsVisible', "Whether editor tabs are visible")); /** * Side by side editor id. @@ -791,7 +792,7 @@ export interface IActiveEditorChangeEvent { * The new active editor or `undefined` if the group is empty. */ editor: EditorInput | undefined; - } +} export interface IEditorWillMoveEvent extends IEditorIdentifier { From 0012ea2fb44ba48f536b327388295cb793f710a9 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 3 Jan 2022 15:52:00 -0800 Subject: [PATCH 1021/2210] Don't include `[` and `]` as strings in markdown links Fixes #139051 --- extensions/markdown-basics/cgmanifest.json | 2 +- .../syntaxes/markdown.tmLanguage.json | 26 +++++++++---------- .../test/colorize-results/test_md.json | 12 ++++----- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/extensions/markdown-basics/cgmanifest.json b/extensions/markdown-basics/cgmanifest.json index c358375dd7591..b2be80b7a4955 100644 --- a/extensions/markdown-basics/cgmanifest.json +++ b/extensions/markdown-basics/cgmanifest.json @@ -33,7 +33,7 @@ "git": { "name": "microsoft/vscode-markdown-tm-grammar", "repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar", - "commitHash": "402cd37bf494f7e04e2df15423ba91ce8a9b1301" + "commitHash": "c2bb8faa5501da4d3a7544b029e5abc5355ef673" } }, "license": "MIT", diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 263501e2b9be4..48870c81d713b 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/402cd37bf494f7e04e2df15423ba91ce8a9b1301", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/c2bb8faa5501da4d3a7544b029e5abc5355ef673", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -2546,13 +2546,13 @@ "image-inline": { "captures": { "1": { - "name": "punctuation.definition.string.begin.markdown" + "name": "punctuation.definition.link.description.begin.markdown" }, "2": { "name": "string.other.link.description.markdown" }, "4": { - "name": "punctuation.definition.string.end.markdown" + "name": "punctuation.definition.link.description.end.markdown" }, "5": { "name": "punctuation.definition.metadata.markdown" @@ -2603,13 +2603,13 @@ "image-ref": { "captures": { "1": { - "name": "punctuation.definition.string.begin.markdown" + "name": "punctuation.definition.link.description.begin.markdown" }, "2": { "name": "string.other.link.description.markdown" }, "4": { - "name": "punctuation.definition.string.begin.markdown" + "name": "punctuation.definition.link.description.end.markdown" }, "5": { "name": "punctuation.definition.constant.markdown" @@ -2718,13 +2718,13 @@ "link-inline": { "captures": { "1": { - "name": "punctuation.definition.string.begin.markdown" + "name": "punctuation.definition.link.title.begin.markdown" }, "2": { "name": "string.other.link.title.markdown" }, "4": { - "name": "punctuation.definition.string.end.markdown" + "name": "punctuation.definition.link.title.end.markdown" }, "5": { "name": "punctuation.definition.metadata.markdown" @@ -2775,13 +2775,13 @@ "link-ref": { "captures": { "1": { - "name": "punctuation.definition.string.begin.markdown" + "name": "punctuation.definition.link.title.begin.markdown" }, "2": { "name": "string.other.link.title.markdown" }, "4": { - "name": "punctuation.definition.string.end.markdown" + "name": "punctuation.definition.link.title.end.markdown" }, "5": { "name": "punctuation.definition.constant.begin.markdown" @@ -2799,13 +2799,13 @@ "link-ref-literal": { "captures": { "1": { - "name": "punctuation.definition.string.begin.markdown" + "name": "punctuation.definition.link.title.begin.markdown" }, "2": { "name": "string.other.link.title.markdown" }, "4": { - "name": "punctuation.definition.string.end.markdown" + "name": "punctuation.definition.link.title.end.markdown" }, "5": { "name": "punctuation.definition.constant.begin.markdown" @@ -2820,13 +2820,13 @@ "link-ref-shortcut": { "captures": { "1": { - "name": "punctuation.definition.string.begin.markdown" + "name": "punctuation.definition.link.title.begin.markdown" }, "2": { "name": "string.other.link.title.markdown" }, "3": { - "name": "punctuation.definition.string.end.markdown" + "name": "punctuation.definition.link.title.end.markdown" } }, "match": "(\\[)(\\S+?)(\\])", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_md.json b/extensions/vscode-colorize-tests/test/colorize-results/test_md.json index 1fee128741772..a13c7a2c98285 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_md.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_md.json @@ -177,7 +177,7 @@ }, { "c": "[", - "t": "text.html.markdown meta.paragraph.markdown meta.link.inline.markdown punctuation.definition.string.begin.markdown", + "t": "text.html.markdown meta.paragraph.markdown meta.link.inline.markdown punctuation.definition.link.title.begin.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -199,7 +199,7 @@ }, { "c": "]", - "t": "text.html.markdown meta.paragraph.markdown meta.link.inline.markdown punctuation.definition.string.end.markdown", + "t": "text.html.markdown meta.paragraph.markdown meta.link.inline.markdown punctuation.definition.link.title.end.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2069,7 +2069,7 @@ }, { "c": "![", - "t": "text.html.markdown meta.paragraph.markdown meta.image.inline.markdown punctuation.definition.string.begin.markdown", + "t": "text.html.markdown meta.paragraph.markdown meta.image.inline.markdown punctuation.definition.link.description.begin.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2091,7 +2091,7 @@ }, { "c": "]", - "t": "text.html.markdown meta.paragraph.markdown meta.image.inline.markdown punctuation.definition.string.end.markdown", + "t": "text.html.markdown meta.paragraph.markdown meta.image.inline.markdown punctuation.definition.link.description.end.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2542,7 +2542,7 @@ }, { "c": "[", - "t": "text.html.markdown meta.paragraph.markdown meta.link.reference.markdown punctuation.definition.string.begin.markdown", + "t": "text.html.markdown meta.paragraph.markdown meta.link.reference.markdown punctuation.definition.link.title.begin.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2564,7 +2564,7 @@ }, { "c": "]", - "t": "text.html.markdown meta.paragraph.markdown meta.link.reference.markdown punctuation.definition.string.end.markdown", + "t": "text.html.markdown meta.paragraph.markdown meta.link.reference.markdown punctuation.definition.link.title.end.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", From c3b939238cd36708982945c68b5ad0819f26bcb7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 5 Jan 2022 11:17:04 -0800 Subject: [PATCH 1022/2210] Fix browser ts version --- .../typescript-language-features/src/extension.browser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/extension.browser.ts b/extensions/typescript-language-features/src/extension.browser.ts index d7ab11db6cd7c..3880a8947924d 100644 --- a/extensions/typescript-language-features/src/extension.browser.ts +++ b/extensions/typescript-language-features/src/extension.browser.ts @@ -58,7 +58,7 @@ export function activate( new TypeScriptVersion( TypeScriptVersionSource.Bundled, vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(), - API.fromSimpleString('4.5.0'))); + API.fromSimpleString('4.5.4'))); const lazyClientHost = createLazyClientHost(context, false, { pluginManager, From 968fa936f515d5c9a0903cab6355f03af2a96233 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 5 Jan 2022 11:23:56 -0800 Subject: [PATCH 1023/2210] Notebook find output filter --- src/vs/editor/contrib/find/findState.ts | 25 +++++++++++++++---- .../browser/find/simpleFindReplaceWidget.css | 2 +- .../browser/find/simpleFindReplaceWidget.ts | 22 ++++++++++++++-- .../browser/contrib/find/findModel.ts | 6 ++--- .../browser/contrib/find/test/find.test.ts | 10 ++++---- .../notebook/browser/notebookEditorWidget.ts | 6 +++++ .../contrib/notebook/common/notebookCommon.ts | 1 + 7 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/contrib/find/findState.ts b/src/vs/editor/contrib/find/findState.ts index 32bf816362946..23bdfd6d0e542 100644 --- a/src/vs/editor/contrib/find/findState.ts +++ b/src/vs/editor/contrib/find/findState.ts @@ -26,6 +26,7 @@ export interface FindReplaceStateChangedEvent { currentMatch: boolean; loop: boolean; isSearching: boolean; + filters: boolean; } export const enum FindOptionOverride { @@ -34,7 +35,7 @@ export const enum FindOptionOverride { False = 2 } -export interface INewFindReplaceState { +export interface INewFindReplaceState { searchString?: string; replaceString?: string; isRevealed?: boolean; @@ -50,6 +51,7 @@ export interface INewFindReplaceState { searchScope?: Range[] | null; loop?: boolean; isSearching?: boolean; + filters?: T; } function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean { @@ -62,7 +64,7 @@ function effectiveOptionValue(override: FindOptionOverride, value: boolean): boo return value; } -export class FindReplaceState extends Disposable { +export class FindReplaceState extends Disposable { private _searchString: string; private _replaceString: string; private _isRevealed: boolean; @@ -81,6 +83,7 @@ export class FindReplaceState extends Disposable { private _currentMatch: Range | null; private _loop: boolean; private _isSearching: boolean; + private _filters: T | null; private readonly _onFindReplaceStateChange = this._register(new Emitter()); public get searchString(): string { return this._searchString; } @@ -102,6 +105,7 @@ export class FindReplaceState extends Disposable { public get matchesCount(): number { return this._matchesCount; } public get currentMatch(): Range | null { return this._currentMatch; } public get isSearching(): boolean { return this._isSearching; } + public get filters(): T | null { return this._filters; } public readonly onFindReplaceStateChange: Event = this._onFindReplaceStateChange.event; constructor() { @@ -124,6 +128,7 @@ export class FindReplaceState extends Disposable { this._currentMatch = null; this._loop = true; this._isSearching = false; + this._filters = null; } public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void { @@ -143,7 +148,8 @@ export class FindReplaceState extends Disposable { matchesCount: false, currentMatch: false, loop: false, - isSearching: false + isSearching: false, + filters: false }; let somethingChanged = false; @@ -178,7 +184,7 @@ export class FindReplaceState extends Disposable { } } - public change(newState: INewFindReplaceState, moveCursor: boolean, updateHistory: boolean = true): void { + public change(newState: INewFindReplaceState, moveCursor: boolean, updateHistory: boolean = true): void { let changeEvent: FindReplaceStateChangedEvent = { moveCursor: moveCursor, updateHistory: updateHistory, @@ -195,7 +201,8 @@ export class FindReplaceState extends Disposable { matchesCount: false, currentMatch: false, loop: false, - isSearching: false + isSearching: false, + filters: false }; let somethingChanged = false; @@ -271,6 +278,14 @@ export class FindReplaceState extends Disposable { } } + if (typeof newState.filters !== 'undefined') { + if (this._filters !== newState.filters) { + this._filters = newState.filters; + changeEvent.filters = true; + somethingChanged = true; + } + } + // Overrides get set when they explicitly come in and get reset anytime something else changes this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet); this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet); diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css index 88f740a1c5656..e70263e3fb6df 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css @@ -9,7 +9,7 @@ position: absolute; top: -45px; right: 18px; - width: 318px; + width: 341px; max-width: calc(100% - 28px - 28px - 8px); pointer-events: none; transition: top 200ms linear; diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts index ce0460c7a1aac..89d141597726a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts @@ -19,14 +19,17 @@ import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/b import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { editorWidgetBackground, editorWidgetForeground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; -import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; +import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { parseReplaceString, ReplacePattern } from 'vs/editor/contrib/find/replacePattern'; +import { Codicon } from 'vs/base/common/codicons'; +import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous Match"); +const NLS_FILTER_BTN_LABEL = nls.localize('label.findFilterButton', "Filter"); const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next Match"); const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace"); @@ -35,6 +38,7 @@ const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Repla const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); +export const findFilterButton = registerIcon('find-filter', Codicon.listFilter, nls.localize('findFilterIcon', 'Icon for Find Filter in find widget.')); export abstract class SimpleFindReplaceWidget extends Widget { protected readonly _findInput: FindInput; @@ -44,6 +48,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { private readonly _findInputFocusTracker: dom.IFocusTracker; private readonly _updateHistoryDelayer: Delayer; protected readonly _matchesCount!: HTMLElement; + protected readonly filterBtn: Checkbox; private readonly prevBtn: SimpleButton; private readonly nextBtn: SimpleButton; @@ -66,7 +71,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { @IContextViewService private readonly _contextViewService: IContextViewService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, - protected readonly _state: FindReplaceState = new FindReplaceState(), + protected readonly _state: FindReplaceState = new FindReplaceState(), showOptionButtons?: boolean ) { super(); @@ -152,6 +157,17 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._matchesCount.className = 'matchesCount'; this._updateMatchesCount(); + // Toggle filter button + this.filterBtn = this._register(new Checkbox({ + title: NLS_FILTER_BTN_LABEL, + icon: findFilterButton, + isChecked: false + })); + + this._register(this.filterBtn.onChange(() => { + this._state.change({ filters: this.filterBtn.checked }, true); + })); + this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, icon: findPreviousMatchIcon, @@ -178,6 +194,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._innerFindDomNode.appendChild(this._findInput.domNode); this._innerFindDomNode.appendChild(this._matchesCount); + this._innerFindDomNode.appendChild(this.filterBtn.domNode); this._innerFindDomNode.appendChild(this.prevBtn.domNode); this._innerFindDomNode.appendChild(this.nextBtn.domNode); this._innerFindDomNode.appendChild(closeBtn.domNode); @@ -318,6 +335,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), }; this._replaceInput.style(replaceStyles); + this.filterBtn.style(inputStyles); } private _onStateChanged(e: FindReplaceStateChangedEvent): void { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index ff9787050f8d0..90091d62edc53 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -40,7 +40,7 @@ export class FindModel extends Disposable { constructor( private readonly _notebookEditor: INotebookEditor, - private readonly _state: FindReplaceState, + private readonly _state: FindReplaceState, @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); @@ -49,7 +49,7 @@ export class FindModel extends Disposable { this._computePromise = null; this._register(_state.onFindReplaceStateChange(e => { - if (e.searchString || e.isRegex || e.matchCase || e.searchScope || e.wholeWord || (e.isRevealed && this._state.isRevealed)) { + if (e.searchString || e.isRegex || e.matchCase || e.searchScope || e.wholeWord || (e.isRevealed && this._state.isRevealed) || e.filters) { this.research(); } @@ -308,7 +308,7 @@ export class FindModel extends Disposable { const val = this._state.searchString; const wordSeparators = this._configurationService.inspect('editor.wordSeparators').value; - const options: INotebookSearchOptions = { regex: this._state.isRegex, wholeWord: this._state.wholeWord, caseSensitive: this._state.matchCase, wordSeparators: wordSeparators }; + const options: INotebookSearchOptions = { regex: this._state.isRegex, wholeWord: this._state.wholeWord, caseSensitive: this._state.matchCase, wordSeparators: wordSeparators, includeOutputs: !!this._state.filters }; if (!val) { ret = null; } else if (!this._notebookEditor.hasModel()) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts index 048679da70847..1bb57887dc2c7 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts @@ -56,7 +56,7 @@ suite('Notebook Find', () => { ], async (editor, viewModel, accessor) => { accessor.stub(IConfigurationService, configurationService); - const state = new FindReplaceState(); + const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); @@ -94,7 +94,7 @@ suite('Notebook Find', () => { async (editor, viewModel, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); - const state = new FindReplaceState(); + const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); @@ -137,7 +137,7 @@ suite('Notebook Find', () => { async (editor, viewModel, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); - const state = new FindReplaceState(); + const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); @@ -173,7 +173,7 @@ suite('Notebook Find', () => { async (editor, viewModel, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); - const state = new FindReplaceState(); + const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); @@ -202,7 +202,7 @@ suite('Notebook Find', () => { ], async (editor, viewModel, accessor) => { accessor.stub(IConfigurationService, configurationService); - const state = new FindReplaceState(); + const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 4aae787cdc91a..0fc185a1275e8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2267,6 +2267,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } const findMatches = this._notebookViewModel.find(query, options).filter(match => match.matches.length > 0); + if (!options.includeOutputs) { + // clear output matches + await this._webview?.findStop(); + return findMatches; + } + const matchMap: { [key: string]: CellFindMatchWithIndex } = {}; findMatches.forEach(match => { matchMap[match.cell.id] = match; diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d61c9cb7ecd15..843fa6cc2f4ee 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -828,6 +828,7 @@ export interface INotebookSearchOptions { wholeWord?: boolean; caseSensitive?: boolean; wordSeparators?: string; + includeOutputs?: boolean; } export interface INotebookExclusiveDocumentFilter { From bdb513f5451a2045b7dc5daef7bd75dd1ac24135 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 5 Jan 2022 14:03:51 -0600 Subject: [PATCH 1024/2210] fix #140128 (#140178) --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 5 +++++ .../workbench/contrib/terminal/browser/terminalInstance.ts | 7 +++++-- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 9e975aa16871c..b1cae1abbf700 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -838,6 +838,11 @@ export interface IXtermTerminal { * viewport. */ clearBuffer(): void; + + /* + * Enables the webgl renderer + */ + enableWebglRenderer(): Promise } export interface IRequestAddInstanceToGroupEvent { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index c61d2db4f962c..67108f0fbf415 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -67,7 +67,7 @@ import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xterm import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/terminalLinkQuickpick'; -import { isFirefox } from 'vs/base/browser/browser'; +import { isFirefox, isSafari } from 'vs/base/browser/browser'; const enum Constants { /** @@ -611,7 +611,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); // Init winpty compat and link handler after process creation as they rely on the // underlying process OS - this._processManager.onProcessReady((processTraits) => { + this._processManager.onProcessReady(async (processTraits) => { // If links are ready, do not re-create the manager. if (this._areLinksReady) { return; @@ -626,6 +626,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._linkManager = this._instantiationService.createInstance(TerminalLinkManager, xterm.raw, this._processManager!); this._areLinksReady = true; this._onLinksReady.fire(this); + if ((!isSafari && this._configHelper.config.gpuAcceleration === 'auto') || this._configHelper.config.gpuAcceleration === 'on') { + await xterm.enableWebglRenderer(); + } }); this._loadTypeAheadAddon(xterm); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 88a573140de27..1f77f27ccd379 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -172,7 +172,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this.raw.options.wordSeparator = config.wordSeparators; this.raw.options.customGlyphs = config.customGlyphs; if ((!isSafari && config.gpuAcceleration === 'auto' && XtermTerminal._suggestedRendererType === undefined) || config.gpuAcceleration === 'on') { - this._enableWebglRenderer(); + this.enableWebglRenderer(); } else { this._disposeOfWebglRenderer(); this.raw.options.rendererType = this._getBuiltInXtermRenderer(config.gpuAcceleration, XtermTerminal._suggestedRendererType); @@ -312,7 +312,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { return rendererType; } - private async _enableWebglRenderer(): Promise { + async enableWebglRenderer(): Promise { if (!this.raw.element || this._webglAddon) { return; } @@ -320,6 +320,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._webglAddon = new Addon(); try { this.raw.loadAddon(this._webglAddon); + this._logService.trace('Webgl was loaded'); this._webglAddon.onContextLoss(() => { this._logService.info(`Webgl lost context, disposing of webgl renderer`); this._disposeOfWebglRenderer(); From d67c235d3b47717f8dd0d79f1b7499bd2664494a Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 5 Jan 2022 12:08:16 -0800 Subject: [PATCH 1025/2210] fix find async test. --- .../browser/contrib/find/test/find.test.ts | 41 +++++++++++++++++++ .../test/browser/testNotebookEditor.ts | 9 ++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts index 1bb57887dc2c7..b6279b57ed4bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/test/find.test.ts @@ -58,8 +58,13 @@ suite('Notebook Find', () => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); + + const found = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); + await found; assert.strictEqual(model.findMatches.length, 2); assert.strictEqual(model.currentMatch, -1); model.find(false); @@ -71,11 +76,15 @@ suite('Notebook Find', () => { assert.strictEqual(editor.textModel.length, 3); + const found2 = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 3, count: 0, cells: [ new TestCell(viewModel.viewType, 3, '# next paragraph 1', 'markdown', CellKind.Code, [], accessor.get(ILanguageService)), ] }], true, undefined, () => undefined, undefined, true); + await found2; assert.strictEqual(editor.textModel.length, 4); assert.strictEqual(model.findMatches.length, 3); assert.strictEqual(model.currentMatch, 0); @@ -96,8 +105,12 @@ suite('Notebook Find', () => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); + const found = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); + await found; // find matches is not necessarily find results assert.strictEqual(model.findMatches.length, 4); assert.strictEqual(model.currentMatch, -1); @@ -108,9 +121,13 @@ suite('Notebook Find', () => { model.find(false); assert.strictEqual(model.currentMatch, 2); + const found2 = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 2, count: 1, cells: [] }], true, undefined, () => undefined, undefined, true); + await found2; assert.strictEqual(model.findMatches.length, 3); assert.strictEqual(model.currentMatch, 2); @@ -139,17 +156,25 @@ suite('Notebook Find', () => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); + const found = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); + await found; // find matches is not necessarily find results assert.strictEqual(model.findMatches.length, 4); assert.strictEqual(model.currentMatch, -1); model.find(true); assert.strictEqual(model.currentMatch, 4); + const found2 = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 2, count: 1, cells: [] }], true, undefined, () => undefined, undefined, true); + await found2; assert.strictEqual(model.findMatches.length, 3); assert.strictEqual(model.currentMatch, 3); model.find(false); @@ -175,8 +200,12 @@ suite('Notebook Find', () => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); + const found = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); + await found; // find matches is not necessarily find results assert.strictEqual(model.findMatches.length, 4); assert.strictEqual(model.currentMatch, -1); @@ -184,11 +213,15 @@ suite('Notebook Find', () => { model.find(false); model.find(false); assert.strictEqual(model.currentMatch, 2); + const found2 = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); (viewModel.viewCells[1].textBuffer as ITextBuffer).applyEdits([ new ValidAnnotatedEditOperation(null, new Range(1, 1, 1, 14), '', false, false, false) ], false, true); // cell content updates, recompute model.research(); + await found2; assert.strictEqual(model.currentMatch, 1); }); }); @@ -204,8 +237,12 @@ suite('Notebook Find', () => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); + const found = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); state.change({ isRevealed: true }, true); state.change({ searchString: '1' }, true); + await found; assert.strictEqual(model.findMatches.length, 2); assert.strictEqual(model.currentMatch, -1); model.find(false); @@ -217,7 +254,11 @@ suite('Notebook Find', () => { assert.strictEqual(editor.textModel.length, 3); + const found2 = new Promise(resolve => state.onFindReplaceStateChange(e => { + if (e.matchesCount) { resolve(true); } + })); state.change({ searchString: '3' }, true); + await found2; assert.strictEqual(model.currentMatch, -1); assert.strictEqual(model.findMatches.length, 0); }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 59e87ed7d5a2c..039a0ebe6a1dd 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -39,7 +39,7 @@ import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { IActiveNotebookEditorDelegate, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFindMatchWithIndex, IActiveNotebookEditorDelegate, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { ListViewInfoAccessor } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; @@ -48,7 +48,7 @@ import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellUri, INotebookDiffEditorModel, INotebookEditorModel, IOutputDto, IResolvedNotebookEditorModel, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, INotebookDiffEditorModel, INotebookEditorModel, INotebookSearchOptions, IOutputDto, IResolvedNotebookEditorModel, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; @@ -289,7 +289,10 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic override get onDidChangeSelection() { return viewModel.onDidChangeSelection as Event; } override get onDidChangeOptions() { return viewModel.onDidChangeOptions; } override get onDidChangeViewCells() { return viewModel.onDidChangeViewCells; } - + override async find(query: string, options: INotebookSearchOptions): Promise { + const findMatches = viewModel.find(query, options).filter(match => match.matches.length > 0); + return findMatches; + } }; return { editor: notebookEditor, viewModel }; From 0dba492e01c5ca1afff8a9de94ba6d8d29ea019f Mon Sep 17 00:00:00 2001 From: Khaled Date: Wed, 5 Jan 2022 12:13:29 -0800 Subject: [PATCH 1026/2210] Corrected the regex to detect espaced percent symbol (#139437) --- .../syntaxes/md-math.tmLanguage.json | 2 +- .../test/colorize-fixtures/md-math.md | 4 ++ .../test/colorize-results/md-math_md.json | 66 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-math/syntaxes/md-math.tmLanguage.json b/extensions/markdown-math/syntaxes/md-math.tmLanguage.json index b7c1b6fc4dfe0..d3146023ecc16 100644 --- a/extensions/markdown-math/syntaxes/md-math.tmLanguage.json +++ b/extensions/markdown-math/syntaxes/md-math.tmLanguage.json @@ -14,7 +14,7 @@ "patterns": [ { "name": "comment.line.math.tex", - "match": "(%)(.+)$", + "match": "((? + +$$ \% Should not be highlighted $$ diff --git a/extensions/vscode-colorize-tests/test/colorize-results/md-math_md.json b/extensions/vscode-colorize-tests/test/colorize-results/md-math_md.json index 51e8f336d9114..e9183132567a7 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/md-math_md.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/md-math_md.json @@ -6532,5 +6532,71 @@ "light_vs": "default: #000000", "hc_black": "default: #FFFFFF" } + }, + { + "c": "", + "t": "text.html.markdown comment.block.html punctuation.definition.comment.html", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "$$", + "t": "text.html.markdown meta.paragraph.markdown markup.math.inline.markdown punctuation.definition.math.begin.markdown", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " \\% Should not be highlighted ", + "t": "text.html.markdown meta.paragraph.markdown markup.math.inline.markdown meta.embedded.math.markdown", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "$$", + "t": "text.html.markdown meta.paragraph.markdown markup.math.inline.markdown punctuation.definition.math.begin.markdown", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } } ] \ No newline at end of file From 154e1f6fb98c258902fd2c89e802745007420ded Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 5 Jan 2022 10:26:05 -0800 Subject: [PATCH 1027/2210] debug: rename view memory action --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index c779d9521595a..88aaf10d0a767 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -144,7 +144,7 @@ registerDebugViewMenuItem(MenuId.DebugCallStackContext, TERMINATE_THREAD_ID, nls registerDebugViewMenuItem(MenuId.DebugCallStackContext, RESTART_FRAME_ID, nls.localize('restartFrame', "Restart Frame"), 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), CONTEXT_RESTART_FRAME_SUPPORTED), CONTEXT_STACK_FRAME_SUPPORTS_RESTART); registerDebugViewMenuItem(MenuId.DebugCallStackContext, COPY_STACK_TRACE_ID, nls.localize('copyStackTrace', "Copy Call Stack"), 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), undefined, '3_modification'); -registerDebugViewMenuItem(MenuId.DebugVariablesContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Memory"), 15, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_IN_DEBUG_MODE, 'inline', icons.debugInspectMemory); +registerDebugViewMenuItem(MenuId.DebugVariablesContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Binary Data"), 15, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_IN_DEBUG_MODE, 'inline', icons.debugInspectMemory); registerDebugViewMenuItem(MenuId.DebugVariablesContext, SET_VARIABLE_ID, nls.localize('setValue', "Set Value"), 10, ContextKeyExpr.or(CONTEXT_SET_VARIABLE_SUPPORTED, ContextKeyExpr.and(CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_SET_EXPRESSION_SUPPORTED)), CONTEXT_VARIABLE_IS_READONLY.toNegated(), '3_modification'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, COPY_VALUE_ID, nls.localize('copyValue', "Copy Value"), 10, undefined, undefined, '5_cutcopypaste'); @@ -158,7 +158,7 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, ADD_WATCH_ID, ADD_WATCH_LABE registerDebugViewMenuItem(MenuId.DebugWatchContext, EDIT_EXPRESSION_COMMAND_ID, nls.localize('editWatchExpression', "Edit Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, SET_EXPRESSION_COMMAND_ID, nls.localize('setValue', "Set Value"), 30, ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), CONTEXT_SET_EXPRESSION_SUPPORTED), ContextKeyExpr.and(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('variable'), CONTEXT_SET_VARIABLE_SUPPORTED)), CONTEXT_VARIABLE_IS_READONLY.toNegated(), '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, COPY_VALUE_ID, nls.localize('copyValue', "Copy Value"), 40, ContextKeyExpr.or(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), CONTEXT_WATCH_ITEM_TYPE.isEqualTo('variable')), CONTEXT_IN_DEBUG_MODE, '3_modification'); -registerDebugViewMenuItem(MenuId.DebugWatchContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Memory"), 50, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_IN_DEBUG_MODE, '3_modification'); +registerDebugViewMenuItem(MenuId.DebugWatchContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Binary Data"), 50, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_IN_DEBUG_MODE, '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_EXPRESSION_COMMAND_ID, nls.localize('removeWatchExpression', "Remove Expression"), 10, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, 'z_commands'); registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, 20, undefined, undefined, 'z_commands'); From 5f90c39e8765a1517fdab5eb3a7eaf0e0aa10bfa Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 5 Jan 2022 10:26:27 -0800 Subject: [PATCH 1028/2210] debug: better experience for installing hex editor --- .../contrib/debug/browser/variablesView.ts | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 42f6f53c37b81..a8e6ed44dcdb8 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -12,7 +12,7 @@ import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IAsyncDataSource, ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { IAction } from 'vs/base/common/actions'; import { coalesce } from 'vs/base/common/arrays'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, timeout } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; @@ -28,7 +28,9 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; @@ -521,10 +523,11 @@ CommandsRegistry.registerCommand({ const commandService = accessor.get(ICommandService); const editorService = accessor.get(IEditorService); - const ext = await accessor.get(IExtensionService).getExtension(HEX_EDITOR_EXTENSION_ID); - if (!ext) { - await commandService.executeCommand('workbench.extensions.search', `@id:${HEX_EDITOR_EXTENSION_ID}`); - } else { + const notifications = accessor.get(INotificationService); + const progressService = accessor.get(IProgressService); + const extensionService = accessor.get(IExtensionService); + const ext = await extensionService.getExtension(HEX_EDITOR_EXTENSION_ID); + if (ext || await tryInstallHexEditor(notifications, progressService, extensionService, commandService)) { await editorService.openEditor({ resource: getUriForDebugMemory(arg.sessionId, arg.variable.memoryReference), options: { @@ -536,6 +539,55 @@ CommandsRegistry.registerCommand({ } }); +function tryInstallHexEditor(notifications: INotificationService, progressService: IProgressService, extensionService: IExtensionService, commandService: ICommandService) { + return new Promise(resolve => { + let installing = false; + + const handle = notifications.prompt( + Severity.Info, + localize("viewMemory.prompt", "Inspecting binary data requires the Hex Editor extension. Would you like to install it now?"), [ + { + label: localize("cancel", "Cancel"), + run: () => resolve(false), + }, + { + label: localize("install", "Install"), + run: async () => { + installing = true; + try { + await progressService.withProgress( + { + location: ProgressLocation.Notification, + title: localize("viewMemory.install.progress", "Installing the Hex Editor..."), + }, + async () => { + await commandService.executeCommand('workbench.extensions.installExtension', HEX_EDITOR_EXTENSION_ID); + // it seems like the extension is not registered immediately on install -- + // wait for it to appear before returning. + while (!(await extensionService.getExtension(HEX_EDITOR_EXTENSION_ID))) { + await timeout(30); + } + }, + ); + resolve(true); + } catch (e) { + notifications.error(e as Error); + resolve(false); + } + } + }, + ], + { sticky: true }, + ); + + handle.onDidClose(e => { + if (!installing) { + resolve(false); + } + }); + }); +} + export const BREAK_WHEN_VALUE_CHANGES_ID = 'debug.breakWhenValueChanges'; CommandsRegistry.registerCommand({ id: BREAK_WHEN_VALUE_CHANGES_ID, From bef5c27e4301148a5ead300fac204e746e7c8fe5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 5 Jan 2022 10:47:35 -0800 Subject: [PATCH 1029/2210] debug: fix view memory action being obstructed by scrollbar --- src/vs/workbench/contrib/debug/browser/media/debugViewlet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index e32056677e8c2..b3adfc060e34e 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -129,7 +129,7 @@ .debug-pane .monaco-list-row .monaco-action-bar { display: none; flex-shrink: 0; - margin-right: 1px; + margin-right: 6px; } .debug-pane .monaco-list-row:hover .monaco-action-bar, From 994f907fa8b39c89440bc01b2486c7380721f786 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 5 Jan 2022 11:11:59 -0800 Subject: [PATCH 1030/2210] debug: fix watcher on debug memory not working --- .../workbench/contrib/debug/browser/debugMemory.ts | 12 +++++++++--- .../workbench/contrib/debug/browser/debugSession.ts | 3 +++ .../contrib/debug/browser/rawDebugSession.ts | 8 ++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugMemory.ts b/src/vs/workbench/contrib/debug/browser/debugMemory.ts index ea17e487be4b6..1686b569fa7e5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugMemory.ts +++ b/src/vs/workbench/contrib/debug/browser/debugMemory.ts @@ -45,11 +45,17 @@ export class DebugMemoryFileSystemProvider implements IFileSystemProvider { return toDisposable(() => { }); } - const { session, memoryReference } = this.parseUri(resource); + const { session, memoryReference, offset } = this.parseUri(resource); return session.onDidInvalidateMemory(e => { - if (e.body.memoryReference === memoryReference) { - this.changeEmitter.fire([{ resource, type: FileChangeType.UPDATED }]); + if (e.body.memoryReference !== memoryReference) { + return; + } + + if (offset && (e.body.offset >= offset.toOffset || e.body.offset + e.body.count < offset.fromOffset)) { + return; } + + this.changeEmitter.fire([{ resource, type: FileChangeType.UPDATED }]); }); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 2b6cedacd13cf..cc89d00e77483 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -1184,6 +1184,9 @@ export class DebugSession implements IDebugSession { this.rawListeners.push(this.raw.onDidProgressEnd(event => { this._onDidProgressEnd.fire(event); })); + this.rawListeners.push(this.raw.onDidInvalidateMemory(event => { + this._onDidInvalidMemory.fire(event); + })); this.rawListeners.push(this.raw.onDidInvalidated(async event => { if (!(event.body.areas && event.body.areas.length === 1 && (event.body.areas[0] === 'variables' || event.body.areas[0] === 'watch'))) { // If invalidated event only requires to update variables or watch, do that, otherwise refatch threads https://github.com/microsoft/vscode/issues/106745 diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 2d8bf4fdddfe6..5c1d0d8332e28 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -69,6 +69,7 @@ export class RawDebugSession implements IDisposable { private readonly _onDidProgressUpdate = new Emitter(); private readonly _onDidProgressEnd = new Emitter(); private readonly _onDidInvalidated = new Emitter(); + private readonly _onDidInvalidateMemory = new Emitter(); private readonly _onDidCustomEvent = new Emitter(); private readonly _onDidEvent = new Emitter(); @@ -155,6 +156,9 @@ export class RawDebugSession implements IDisposable { case 'invalidated': this._onDidInvalidated.fire(event as DebugProtocol.InvalidatedEvent); break; + case 'memory': + this._onDidInvalidateMemory.fire(event as DebugProtocol.MemoryEvent); + break; case 'process': break; case 'module': @@ -243,6 +247,10 @@ export class RawDebugSession implements IDisposable { return this._onDidInvalidated.event; } + get onDidInvalidateMemory(): Event { + return this._onDidInvalidateMemory.event; + } + get onDidEvent(): Event { return this._onDidEvent.event; } From 973363e6a8343fcd52d2d0d4949d311eef325db6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 5 Jan 2022 15:03:04 -0600 Subject: [PATCH 1031/2210] add shell integration addon --- src/vs/platform/terminal/common/terminal.ts | 7 ++-- src/vs/platform/terminal/node/ptyService.ts | 33 ++++++++++++++++++- .../platform/terminal/node/terminalProcess.ts | 7 +++- .../contrib/terminal/browser/remotePty.ts | 3 +- .../terminal/electron-sandbox/localPty.ts | 3 +- 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 87f55e99f74f3..071239dda238f 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -191,7 +191,8 @@ export const enum ProcessPropertyType { ShellType = 'shellType', HasChildProcesses = 'hasChildProcesses', ResolvedShellLaunchConfig = 'resolvedShellLaunchConfig', - OverrideDimensions = 'overrideDimensions' + OverrideDimensions = 'overrideDimensions', + Capability = 'capability' } export interface IProcessProperty { @@ -208,6 +209,7 @@ export interface IProcessPropertyMap { [ProcessPropertyType.HasChildProcesses]: boolean, [ProcessPropertyType.ResolvedShellLaunchConfig]: IShellLaunchConfig, [ProcessPropertyType.OverrideDimensions]: ITerminalDimensionsOverride | undefined + [ProcessPropertyType.Capability]: ProcessCapability | undefined } export interface IFixedTerminalDimensions { @@ -548,7 +550,8 @@ export interface IProcessReadyEvent { } export const enum ProcessCapability { - CwdDetection = 'cwdDetection' + CwdDetection = 'cwdDetection', + CommandCognisant = 'commandCognisant' } /** diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 5768aab671e77..26c5980923117 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -15,7 +15,7 @@ import { RequestStore } from 'vs/platform/terminal/common/requestStore'; import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, IFixedTerminalDimensions, ProcessCapability, IPersistentTerminalProcessLaunchOptions, ICrossVersionSerializedTerminalState, ISerializedTerminalState } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; -import { Terminal as XtermTerminal } from 'xterm-headless'; +import { ITerminalAddon, Terminal as XtermTerminal } from 'xterm-headless'; import type { ISerializeOptions, SerializeAddon as XtermSerializeAddon } from 'xterm-addon-serialize'; import type { Unicode11Addon as XtermUnicode11Addon } from 'xterm-addon-unicode11'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto } from 'vs/platform/terminal/common/terminalProcess'; @@ -201,6 +201,11 @@ export class PtyService extends Disposable implements IPtyService { persistentProcess.installAutoReply(e[0], e[1]); } }); + const shellIntegrationAddon = new ShellIntegrationAddon(); + shellIntegrationAddon.onDidStartShellIntegration(() => { + this._logService.info('updating shell'); + process.updateProperty(ProcessPropertyType.Capability, ProcessCapability.CommandCognisant); + }); this._ptys.set(id, persistentProcess); return id; } @@ -501,6 +506,10 @@ export class PersistentTerminalProcess extends Disposable { unicodeVersion, reviveBuffer ); + const shellIntegrationAddon = new ShellIntegrationAddon(); + shellIntegrationAddon.onDidStartShellIntegration(() => { + this._terminalProcess.updateProperty(ProcessPropertyType.Capability, ProcessCapability.CommandCognisant); + }); this._fixedDimensions = fixedDimensions; this._orphanQuestionBarrier = null; this._orphanQuestionReplyTime = 0; @@ -564,6 +573,11 @@ export class PersistentTerminalProcess extends Disposable { async updateProperty(type: T, value: IProcessPropertyMap[T]): Promise { if (type === ProcessPropertyType.FixedDimensions) { return this._setFixedDimensions(value as IProcessPropertyMap[ProcessPropertyType.FixedDimensions]); + } else if (type === ProcessPropertyType.Capability) { + //TODO:? + // // if (!this.capabilities.find(c => c === value) && value) { + // this.capabilities.push(value); + // } } } @@ -721,6 +735,23 @@ export class PersistentTerminalProcess extends Disposable { } } +class ShellIntegrationAddon extends Disposable implements ITerminalAddon { + private readonly _onDidStartShellIntegration = this._register(new Emitter()); + readonly onDidStartShellIntegration = this._onDidStartShellIntegration.event; + activate(terminal: XtermTerminal): void { + terminal.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); + } + private _handleShellIntegration(data: string): boolean { + const [command,] = data.split(';'); + switch (command) { + case 'E': + this._onDidStartShellIntegration.fire(); + default: + return false; + } + } +} + class XtermSerializer implements ITerminalSerializer { private _xterm: XtermTerminal; private _unicodeAddon?: XtermUnicode11Addon; diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 37356738a4eb5..d18ced72ea863 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -84,7 +84,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined + overrideDimensions: undefined, + capability: ProcessCapability.CommandCognisant }; private static _lastKillOrStart = 0; private _exitCode: number | undefined; @@ -425,6 +426,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess async updateProperty(type: T, value: IProcessPropertyMap[T]): Promise { if (type === ProcessPropertyType.FixedDimensions) { this._properties.fixedDimensions = value as IProcessPropertyMap[ProcessPropertyType.FixedDimensions]; + } else if (type === ProcessPropertyType.Capability) { + if (!this.capabilities.find(c => c === value) && value) { + this.capabilities.push(value as ProcessCapability); + } } } diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index 1f5a709a94124..df1c256180416 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -35,7 +35,8 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined + overrideDimensions: undefined, + capability: undefined }; private _capabilities: ProcessCapability[] = []; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts index 84a3f8b1fb595..b584e01ca6e9d 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -24,7 +24,8 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined + overrideDimensions: undefined, + capability: undefined }; private _capabilities: ProcessCapability[] = []; get capabilities(): ProcessCapability[] { return this._capabilities; } From 42b3d8a06d5c694a6611d60f83b83c22e57fd8cc Mon Sep 17 00:00:00 2001 From: iamkun Date: Thu, 6 Jan 2022 05:51:08 +0800 Subject: [PATCH 1032/2210] refactor: remove duplicate if statement (#140085) --- src/vs/editor/browser/view/viewPart.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/editor/browser/view/viewPart.ts b/src/vs/editor/browser/view/viewPart.ts index 7b42820164c45..2da12f053ac0b 100644 --- a/src/vs/editor/browser/view/viewPart.ts +++ b/src/vs/editor/browser/view/viewPart.ts @@ -42,11 +42,7 @@ export const enum PartFingerprint { export class PartFingerprints { public static write(target: Element | FastDomNode, partId: PartFingerprint) { - if (target instanceof FastDomNode) { - target.setAttribute('data-mprt', String(partId)); - } else { - target.setAttribute('data-mprt', String(partId)); - } + target.setAttribute('data-mprt', String(partId)); } public static read(target: Element): PartFingerprint { From 7e4ed924c107fcf0eb5da96d57c3e90760d919db Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 5 Jan 2022 14:05:45 -0800 Subject: [PATCH 1033/2210] Pick up new TS version for building VS Code --- build/package.json | 2 +- build/yarn.lock | 8 ++++---- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/package.json b/build/package.json index 6afc39284017a..4da4e73c21160 100644 --- a/build/package.json +++ b/build/package.json @@ -62,7 +62,7 @@ "plist": "^3.0.1", "source-map": "0.6.1", "tmp": "^0.2.1", - "typescript": "^4.6.0-dev.20211115", + "typescript": "^4.6.0-dev.20220105", "vsce": "^1.100.0", "vscode-universal-bundler": "^0.0.2" }, diff --git a/build/yarn.lock b/build/yarn.lock index 2eb5dcd484d85..bc3b438dbcc27 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2642,10 +2642,10 @@ typed-rest-client@^1.8.4: tunnel "0.0.6" underscore "^1.12.1" -typescript@^4.6.0-dev.20211115: - version "4.6.0-dev.20211115" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20211115.tgz#215e5d032e77cb83f382dc88e901a0757c02cc53" - integrity sha512-rYdYp/j8OhCRFs97l7GNOX9xGHndwwgY8AcL7LDzmFXgBOXC2VLoQP48nCg8FgVzjK6s0M5V4nijTYHRlwiqGQ== +typescript@^4.6.0-dev.20220105: + version "4.6.0-dev.20220105" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20220105.tgz#b579d05337689e6a15abb706cead8b72bd5b94af" + integrity sha512-smTVpBwnw0ePDLMkknx++AYB0iFJZT980QBr41p0AOqZdycxOwimVly/u5Ew+iLpK0Ksg0Xn5YxO+AeKqa4AjA== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" diff --git a/package.json b/package.json index da691d1e32e78..ac67f7ffbee6d 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,7 @@ "style-loader": "^1.0.0", "ts-loader": "^9.2.3", "tsec": "0.1.4", - "typescript": "^4.6.0-dev.20211115", + "typescript": "^4.6.0-dev.20220105", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/yarn.lock b/yarn.lock index a89d807a3e26a..67d373631dfe5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10017,10 +10017,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^4.6.0-dev.20211115: - version "4.6.0-dev.20211115" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20211115.tgz#215e5d032e77cb83f382dc88e901a0757c02cc53" - integrity sha512-rYdYp/j8OhCRFs97l7GNOX9xGHndwwgY8AcL7LDzmFXgBOXC2VLoQP48nCg8FgVzjK6s0M5V4nijTYHRlwiqGQ== +typescript@^4.6.0-dev.20220105: + version "4.6.0-dev.20220105" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20220105.tgz#b579d05337689e6a15abb706cead8b72bd5b94af" + integrity sha512-smTVpBwnw0ePDLMkknx++AYB0iFJZT980QBr41p0AOqZdycxOwimVly/u5Ew+iLpK0Ksg0Xn5YxO+AeKqa4AjA== typical@^4.0.0: version "4.0.0" From 7f917e28eda952cac9aa4f14c192cc18db4c1b83 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Jan 2022 14:10:41 -0800 Subject: [PATCH 1034/2210] Remove SSH as it's part of authority --- src/vs/platform/launch/electron-main/launchMainService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 3ad5aac15a5dd..55fd15b3099e8 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -266,7 +266,7 @@ export class LaunchMainService implements ILaunchMainService { }); setTimeout(() => { - resolve({ hostName: remoteAuthority, errorMessage: `Connection to 'SSH:${remoteAuthority}' could not be established` }); + resolve({ hostName: remoteAuthority, errorMessage: `Connection to '${remoteAuthority}' could not be established` }); }, 5000); } else { resolve(undefined); From 6cd5790320d8bf123b05743aaf1661a2650f245f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Jan 2022 14:11:22 -0800 Subject: [PATCH 1035/2210] Remove SSH as it's part of host --- .../contrib/remote/electron-sandbox/remote.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts index 3ebb28cc0bb2f..408b8affa2db8 100644 --- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts @@ -66,7 +66,7 @@ class RemoteAgentDiagnosticListener implements IWorkbenchContribution { ipcRenderer.send(request.replyChannel, info); }) .catch(e => { - const errorMessage = e && e.message ? `Connection to 'SSH: ${hostName}' could not be established ${e.message}` : `Connection to 'SSH: ${hostName}' could not be established `; + const errorMessage = e && e.message ? `Connection to '${hostName}' could not be established ${e.message}` : `Connection to '${hostName}' could not be established `; ipcRenderer.send(request.replyChannel, { hostName, errorMessage }); }); } else { From 2a0d371ca3a7c216185eb8f4bc3831420b3c9d1c Mon Sep 17 00:00:00 2001 From: Mohammad Sadegh Salimi <45296858+SMSadegh19@users.noreply.github.com> Date: Thu, 6 Jan 2022 01:44:32 +0330 Subject: [PATCH 1036/2210] Adding support for RTL languages for readme markdown preview. (#139644) * [markdown preview] dir "auto" added to the markdown-body class. * fix markdown preview for RTL languages --- .../src/features/previewContentProvider.ts | 2 +- extensions/markdown-language-features/src/markdownEngine.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-language-features/src/features/previewContentProvider.ts b/extensions/markdown-language-features/src/features/previewContentProvider.ts index 6d86de5351fab..8a973f9faa025 100644 --- a/extensions/markdown-language-features/src/features/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/features/previewContentProvider.ts @@ -111,7 +111,7 @@ export class MarkdownContentProvider { resourceProvider: WebviewResourceProvider, ): Promise { const rendered = await this.engine.render(markdownDocument, resourceProvider); - const html = `
    ${rendered.html}
    `; + const html = `
    ${rendered.html}
    `; return { html, containingImages: rendered.containingImages diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 8460b71dcd8a8..72bdba51fcb5f 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -25,6 +25,7 @@ const pluginSourceMap: MarkdownIt.PluginSimple = (md): void => { if (token.map && token.type !== 'inline') { token.attrSet('data-line', String(token.map[0])); token.attrJoin('class', 'code-line'); + token.attrJoin('dir', 'auto'); } } }); @@ -178,7 +179,7 @@ export class MarkdownEngine { return engine.parse(text.replace(UNICODE_NEWLINE_REGEX, ''), {}); } - + public resetSlugCount(): void { this._slugCount = new Map(); } From a35a6ecc13c8a200a156cb50cb65d7099b42164f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Jan 2022 14:42:26 -0800 Subject: [PATCH 1037/2210] Stub editor service in terminal test Will fix warning. Fixes #139982 --- .../contrib/terminal/test/browser/terminalService.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts index c26cd02627e6b..9ab2dc7f95ff3 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts @@ -12,12 +12,13 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestLifecycleService, TestTerminalEditorService, TestTerminalGroupService, TestTerminalInstanceService, TestTerminalProfileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService, TestLifecycleService, TestTerminalEditorService, TestTerminalGroupService, TestTerminalInstanceService, TestTerminalProfileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -44,6 +45,7 @@ suite('Workbench - TerminalService', () => { instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); instantiationService.stub(ILifecycleService, new TestLifecycleService()); instantiationService.stub(IThemeService, new TestThemeService()); + instantiationService.stub(IEditorService, new TestEditorService()); instantiationService.stub(ITerminalEditorService, new TestTerminalEditorService()); instantiationService.stub(ITerminalGroupService, new TestTerminalGroupService()); instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService()); From ef69c9c8d8685c3c4b3f1c91614878d2c0141ea1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 5 Jan 2022 14:49:00 -0800 Subject: [PATCH 1038/2210] Fix markdown pinning test --- .../markdown-language-features/src/test/engine.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/markdown-language-features/src/test/engine.test.ts b/extensions/markdown-language-features/src/test/engine.test.ts index 6091edb4b929c..8a6466781ceb3 100644 --- a/extensions/markdown-language-features/src/test/engine.test.ts +++ b/extensions/markdown-language-features/src/test/engine.test.ts @@ -15,8 +15,8 @@ const testFileName = vscode.Uri.file('test.md'); suite('markdown.engine', () => { suite('rendering', () => { const input = '# hello\n\nworld!'; - const output = '

    hello

    \n' - + '

    world!

    \n'; + const output = '

    hello

    \n' + + '

    world!

    \n'; test('Renders a document', async () => { const doc = new InMemoryDocument(testFileName, input); @@ -36,7 +36,7 @@ suite('markdown.engine', () => { test('Extracts all images', async () => { const engine = createNewMarkdownEngine(); assert.deepStrictEqual((await engine.render(input)), { - html: '

    ' + html: '

    ' + ' ' + ' ' + ' ' From 9ed5a855c131128021e991435e6fde299a7fd1b1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 5 Jan 2022 14:53:44 -0800 Subject: [PATCH 1039/2210] Add support for office-script virtual file system --- .../src/typescriptServiceClient.ts | 6 +++++- .../typescript-language-features/src/utils/fileSchemes.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index debd125ffcd50..f95fb60e84bb6 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -740,7 +740,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType public getWorkspaceRootForResource(resource: vscode.Uri): string | undefined { const roots = vscode.workspace.workspaceFolders ? Array.from(vscode.workspace.workspaceFolders) : undefined; - if (!roots || !roots.length) { + if (!roots?.length) { + if (resource.scheme === fileSchemes.officeScript) { + return '/'; + } return undefined; } @@ -750,6 +753,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType case fileSchemes.vscodeNotebookCell: case fileSchemes.memFs: case fileSchemes.vscodeVfs: + case fileSchemes.officeScript: for (const root of roots.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)) { if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) { return root.uri.fsPath; diff --git a/extensions/typescript-language-features/src/utils/fileSchemes.ts b/extensions/typescript-language-features/src/utils/fileSchemes.ts index 69c54ac62e4dc..64b4982cfa3a0 100644 --- a/extensions/typescript-language-features/src/utils/fileSchemes.ts +++ b/extensions/typescript-language-features/src/utils/fileSchemes.ts @@ -12,6 +12,7 @@ export const walkThroughSnippet = 'walkThroughSnippet'; export const vscodeNotebookCell = 'vscode-notebook-cell'; export const memFs = 'memfs'; export const vscodeVfs = 'vscode-vfs'; +export const officeScript = 'office-script'; export const semanticSupportedSchemes = [ file, From 80ce88bc6a707afd50b39c350d49a5ed9c4cb84e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 5 Jan 2022 15:13:35 -0800 Subject: [PATCH 1040/2210] Adopt strike through for markdown Fixes #43504 Picks up new markdown grammar with strikethrough support and also adopts it for our built-in themes --- extensions/markdown-basics/cgmanifest.json | 2 +- .../syntaxes/markdown.tmLanguage.json | 81 ++++++++++++++++++- .../theme-abyss/themes/abyss-color-theme.json | 6 ++ extensions/theme-defaults/themes/dark_vs.json | 6 ++ .../theme-defaults/themes/hc_black.json | 6 ++ .../theme-defaults/themes/light_vs.json | 6 ++ .../themes/kimbie-dark-color-theme.json | 6 ++ .../themes/dimmed-monokai-color-theme.json | 6 ++ .../themes/monokai-color-theme.json | 6 ++ .../themes/quietlight-color-theme.json | 6 ++ .../theme-red/themes/Red-color-theme.json | 6 ++ .../themes/solarized-dark-color-theme.json | 6 ++ .../themes/solarized-light-color-theme.json | 6 ++ .../tomorrow-night-blue-color-theme.json | 6 ++ 14 files changed, 152 insertions(+), 3 deletions(-) diff --git a/extensions/markdown-basics/cgmanifest.json b/extensions/markdown-basics/cgmanifest.json index b2be80b7a4955..933fdc4d3140b 100644 --- a/extensions/markdown-basics/cgmanifest.json +++ b/extensions/markdown-basics/cgmanifest.json @@ -33,7 +33,7 @@ "git": { "name": "microsoft/vscode-markdown-tm-grammar", "repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar", - "commitHash": "c2bb8faa5501da4d3a7544b029e5abc5355ef673" + "commitHash": "a8545b220dc2cb109835571ba3458ff138e97361" } }, "license": "MIT", diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 48870c81d713b..e91e0f6ea6044 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/c2bb8faa5501da4d3a7544b029e5abc5355ef673", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/a8545b220dc2cb109835571ba3458ff138e97361", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -2436,6 +2436,9 @@ { "include": "#raw" }, + { + "include": "#strikethrough" + }, { "include": "#escape" }, @@ -2531,6 +2534,9 @@ }, { "include": "#link-ref-shortcut" + }, + { + "include": "#strikethrough" } ] }, @@ -2682,6 +2688,9 @@ }, { "include": "#link-ref-shortcut" + }, + { + "include": "#strikethrough" } ] }, @@ -2841,8 +2850,76 @@ "name": "punctuation.definition.raw.markdown" } }, - "match": "(`+)([^`]|(?!(?]*?>)", + "end": "(?<=>)", + "patterns": [ + { + "include": "text.html.derivative" + } + ] + }, + { + "include": "#escape" + }, + { + "include": "#ampersand" + }, + { + "include": "#bracket" + }, + { + "include": "#raw" + }, + { + "include": "#bold" + }, + { + "include": "#italic" + }, + { + "include": "#image-inline" + }, + { + "include": "#link-inline" + }, + { + "include": "#link-inet" + }, + { + "include": "#link-email" + }, + { + "include": "#image-ref" + }, + { + "include": "#link-ref-literal" + }, + { + "include": "#link-ref" + }, + { + "include": "#link-ref-shortcut" + } + ] + }, + "3": { + "name": "punctuation.definition.strikethrough.markdown" + } + }, + "match": "(~+)((?:[^~]|(?!(? Date: Wed, 5 Jan 2022 17:27:09 -0600 Subject: [PATCH 1041/2210] improve terminal link keyboard navigation experience (#140121) --- .../browser/links/terminalLinkManager.ts | 49 +++++++------ .../browser/links/terminalLinkQuickpick.ts | 70 +++++++++++++++++++ .../links/terminalProtocolLinkProvider.ts | 2 +- .../terminalValidatedLocalLinkProvider.ts | 2 +- .../browser/links/terminalWordLinkProvider.ts | 2 +- .../contrib/terminal/browser/terminal.ts | 7 +- .../terminal/browser/terminalActions.ts | 35 ++-------- .../terminal/browser/terminalInstance.ts | 34 ++++++--- .../terminal/browser/terminalLinkQuickpick.ts | 63 ----------------- 9 files changed, 130 insertions(+), 134 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick.ts delete mode 100644 src/vs/workbench/contrib/terminal/browser/terminalLinkQuickpick.ts diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index 25f8be5ad0846..89eb2bfcf0520 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -16,7 +16,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import type { Terminal, IViewportRange, ILinkProvider, ILink } from 'xterm'; import { Schemas } from 'vs/base/common/network'; import { posix, win32 } from 'vs/base/common/path'; -import { ITerminalExternalLinkProvider, ITerminalInstance, TerminalLinkQuickpickEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalExternalLinkProvider, ITerminalInstance, TerminalLinkQuickPickEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { OperatingSystem, isMacintosh, OS } from 'vs/base/common/platform'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { TerminalProtocolLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider'; @@ -44,7 +44,7 @@ interface IPath { export class TerminalLinkManager extends DisposableStore { private _widgetManager: TerminalWidgetManager | undefined; private _processCwd: string | undefined; - private _standardLinkProviders: ILinkProvider[] = []; + private _standardLinkProviders: Map = new Map(); private _linkProvidersDisposables: IDisposable[] = []; constructor( @@ -67,7 +67,7 @@ export class TerminalLinkManager extends DisposableStore { this._wrapLinkHandler.bind(this), this._tooltipCallback.bind(this), async (link, cb) => cb(await this._resolvePath(link))); - this._standardLinkProviders.push(protocolProvider); + this._standardLinkProviders.set(TerminalProtocolLinkProvider.id, protocolProvider); // Validated local links if (this._configurationService.getValue(TERMINAL_CONFIG_SECTION).enableFileLinks) { @@ -87,29 +87,32 @@ export class TerminalLinkManager extends DisposableStore { } return cb(undefined); }); - this._standardLinkProviders.push(validatedProvider); + this._standardLinkProviders.set(TerminalValidatedLocalLinkProvider.id, validatedProvider); } // Word links const wordProvider = this._instantiationService.createInstance(TerminalWordLinkProvider, this._xterm, this._wrapLinkHandler.bind(this), this._tooltipCallback.bind(this)); - this._standardLinkProviders.push(wordProvider); + this._standardLinkProviders.set(TerminalWordLinkProvider.id, wordProvider); this._registerStandardLinkProviders(); } - async getLinks(type: TerminalLinkProviderType, y: number): Promise { - let provider: ILinkProvider | undefined = undefined; - if (type === TerminalLinkProviderType.Word) { - provider = this._standardLinkProviders.find(p => p instanceof TerminalWordLinkProvider); - } else if (type === TerminalLinkProviderType.Validated) { - provider = this._standardLinkProviders.find(p => p instanceof TerminalValidatedLocalLinkProvider); - } else { - provider = this._standardLinkProviders.find(p => p instanceof TerminalProtocolLinkProvider); - } - if (provider === undefined) { - throw new Error(`no link provider of type ${type}`); + async getLinks(y: number): Promise { + let unfilteredWordLinks = (await new Promise(r => this._standardLinkProviders.get(TerminalWordLinkProvider.id)?.provideLinks(y, r))); + const webLinks = (await new Promise(r => this._standardLinkProviders.get(TerminalProtocolLinkProvider.id)?.provideLinks(y, r))); + const fileLinks = (await new Promise(r => this._standardLinkProviders.get(TerminalValidatedLocalLinkProvider.id)?.provideLinks(y, r))); + const words = new Set(); + let wordLinks; + if (unfilteredWordLinks) { + wordLinks = []; + for (const link of unfilteredWordLinks) { + if (!words.has(link.text)) { + wordLinks.push(link); + words.add(link.text); + } + } } - return (await new Promise(r => provider?.provideLinks(y, r)))!; + return { wordLinks, webLinks, fileLinks }; } private _tooltipCallback(link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) { if (!this._widgetManager) { @@ -165,7 +168,7 @@ export class TerminalLinkManager extends DisposableStore { } private _registerStandardLinkProviders(): void { - for (const p of this._standardLinkProviders) { + for (const p of this._standardLinkProviders.values()) { this._linkProvidersDisposables.push(this._xterm.registerLinkProvider(p)); } } @@ -186,7 +189,7 @@ export class TerminalLinkManager extends DisposableStore { event?.preventDefault(); // Require correct modifier on click unless event is coming from linkQuickPick selection - if (event && !(event instanceof TerminalLinkQuickpickEvent) && !this._isLinkActivationModifierDown(event)) { + if (event && !(event instanceof TerminalLinkQuickPickEvent) && !this._isLinkActivationModifierDown(event)) { return; } @@ -439,8 +442,8 @@ export interface LineColumnInfo { columnNumber: number; } -export enum TerminalLinkProviderType { - Word = 'word', - Validated = 'validated', - Protocol = 'protocol' +export interface IDetectedLinks { + wordLinks?: ILink[]; + webLinks?: ILink[]; + fileLinks?: ILink[]; } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick.ts new file mode 100644 index 0000000000000..3d0f1be775402 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EventType } from 'vs/base/browser/dom'; +import { localize } from 'vs/nls'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IDetectedLinks } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; +import { TerminalLinkQuickPickEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ILink } from 'xterm'; + +export class TerminalLinkQuickpick { + constructor( + @IQuickInputService private readonly _quickInputService: IQuickInputService + ) { } + + async show(links: IDetectedLinks): Promise { + const wordPicks = links.wordLinks ? await this._generatePicks(links.wordLinks) : undefined; + const filePicks = links.fileLinks ? await this._generatePicks(links.fileLinks) : undefined; + const webPicks = links.webLinks ? await this._generatePicks(links.webLinks) : undefined; + const options = { + placeHolder: localize('terminal.integrated.openWebLink', "Select the link to open"), + canPickMany: false + }; + const picks: LinkQuickPickItem[] = []; + if (webPicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.webLinks', "Web") }); + picks.push(...webPicks); + } + if (filePicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.fileLinks', "File") }); + picks.push(...filePicks); + } + if (wordPicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.wordLinks', "Word") }); + picks.push(...wordPicks); + } + + const pick = await this._quickInputService.pick(picks, options); + if (!pick) { + return; + } + const event = new TerminalLinkQuickPickEvent(EventType.CLICK); + pick.link.activate(event, pick.label); + return; + } + + private async _generatePicks(links: ILink[]): Promise { + if (!links) { + return; + } + const linkKeys: Set = new Set(); + const picks: ITerminalLinkQuickPickItem[] = []; + for (const link of links) { + const label = link.text; + if (!linkKeys.has(label)) { + linkKeys.add(label); + picks.push({ label, link }); + } + } + return picks.length > 0 ? picks : undefined; + } +} + +export interface ITerminalLinkQuickPickItem extends IQuickPickItem { + link: ILink +} + +type LinkQuickPickItem = ITerminalLinkQuickPickItem | IQuickPickSeparator; diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider.ts index b08b9070d9dba..4ad7b54eb2905 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider.ts @@ -19,7 +19,7 @@ import { Schemas } from 'vs/base/common/network'; export class TerminalProtocolLinkProvider extends TerminalBaseLinkProvider { private _linkComputerTarget: ILinkComputerTarget | undefined; - + static id: string = 'TerminalProtocolLinkProvider'; constructor( private readonly _xterm: Terminal, private readonly _activateCallback: (event: MouseEvent | undefined, uri: string) => void, diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts index 9c33e62a285e5..80c855c5b910a 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts @@ -56,7 +56,7 @@ const cachedValidatedLinks = new Map void) => XtermLinkMatcherHandler, diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index b1cae1abbf700..d46a298916216 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -17,7 +17,6 @@ import { DeserializedTerminalEditorInput } from 'vs/workbench/contrib/terminal/b import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; -import { TerminalLinkProviderType } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalEditorService = createDecorator('terminalEditorService'); @@ -198,7 +197,7 @@ export interface ITerminalService extends ITerminalInstanceHost { toggleDevTools(open?: boolean): Promise; handleNewRegisteredBackend(backend: ITerminalBackend): void; } -export class TerminalLinkQuickpickEvent extends MouseEvent { +export class TerminalLinkQuickPickEvent extends MouseEvent { } export interface ITerminalServiceNativeDelegate { @@ -790,8 +789,10 @@ export interface ITerminalInstance { /** * Triggers a quick pick that displays links from the viewport of the active terminal. + * Selecting a file or web link will open it. Selecting a word link will copy it to the + * clipboard. */ - showLinkQuickpick(type: TerminalLinkProviderType): Promise; + showLinkQuickpick(): Promise; } export interface IXtermTerminal { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index fe3692606f084..99ddb4fba685b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -50,7 +50,6 @@ import { AbstractVariableResolverService } from 'vs/workbench/services/configura import { ITerminalQuickPickItem } from 'vs/workbench/contrib/terminal/browser/terminalProfileQuickpick'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { getIconId, getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; -import { TerminalLinkProviderType } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); @@ -1893,48 +1892,22 @@ export function registerTerminalActions() { accessor.get(ITerminalService).doWithActiveInstance(t => t.clearBuffer()); } }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: TerminalCommandId.ShowWordLinkQuickpick, - title: { value: localize('workbench.action.terminal.showWordLinkQuickpick', "Show Word Link Quick Pick"), original: 'Show Word Link Quickpick' }, - f1: true, - category, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - }); - } - run(accessor: ServicesAccessor) { - accessor.get(ITerminalService).doWithActiveInstance(t => t.showLinkQuickpick(TerminalLinkProviderType.Word)); - } - }); + registerAction2(class extends Action2 { constructor() { super({ id: TerminalCommandId.ShowProtocolLinkQuickpick, - title: { value: localize('workbench.action.terminal.showProtocolLinkQuickpick', "Show Protocol Link Quick Pick"), original: 'Show Protocol Link Quickpick' }, + title: { value: localize('workbench.action.terminal.selectDetectedLink', "Select Detected Link"), original: 'Select Detected Link' }, f1: true, category, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), }); } run(accessor: ServicesAccessor) { - accessor.get(ITerminalService).doWithActiveInstance(t => t.showLinkQuickpick(TerminalLinkProviderType.Protocol)); - } - }); - registerAction2(class extends Action2 { - constructor() { - super({ - id: TerminalCommandId.ShowValidatedLinkQuickpick, - title: { value: localize('workbench.action.terminal.showValidatedLinkQuickpick', "Show Validated Link Quick Pick"), original: 'Show Validated Link Quickpick' }, - f1: true, - category, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - }); - } - run(accessor: ServicesAccessor) { - accessor.get(ITerminalService).doWithActiveInstance(t => t.showLinkQuickpick(TerminalLinkProviderType.Validated)); + accessor.get(ITerminalService).doWithActiveInstance(t => t.showLinkQuickpick()); } }); + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 67108f0fbf415..ad4c2be84a0e5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,7 +25,7 @@ import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticip import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager, ProcessState, TERMINAL_VIEW_ID, INavigationMode, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, ITerminalProfileResolverService, TerminalCommandId, ITerminalBackend } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; -import { TerminalLinkManager, TerminalLinkProviderType } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; +import { IDetectedLinks, TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstance, ITerminalExternalLinkProvider, IRequestAddInstanceToGroupEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; @@ -66,8 +66,8 @@ import { LineDataEventAddon } from 'vs/workbench/contrib/terminal/browser/xterm/ import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; -import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/terminalLinkQuickpick'; import { isFirefox, isSafari } from 'vs/base/browser/browser'; +import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; const enum Constants { /** @@ -660,32 +660,44 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - async showLinkQuickpick(type: TerminalLinkProviderType): Promise { + async showLinkQuickpick(): Promise { if (!this._terminalLinkQuickpick) { this._terminalLinkQuickpick = this._instantiationService.createInstance(TerminalLinkQuickpick); } - const links = await this._getLinks(type); + const links = await this._getLinks(); if (!links) { return; } - return await this._terminalLinkQuickpick.show(type, links); + return await this._terminalLinkQuickpick.show(links); } - private async _getLinks(type: TerminalLinkProviderType): Promise { + private async _getLinks(): Promise { if (!this.areLinksReady || !this._linkManager) { throw new Error('terminal links are not ready, cannot generate link quick pick'); } if (!this.xterm) { throw new Error('no xterm'); } - const links = []; + const wordResults: ILink[] = []; + const webResults: ILink[] = []; + const fileResults: ILink[] = []; + for (let i = this.xterm.raw.buffer.active.length - 1; i >= this.xterm.raw.buffer.active.viewportY; i--) { - const linksForY = await this._linkManager.getLinks(type, i); - if (linksForY) { - links.push(...linksForY); + const links = await this._linkManager.getLinks(i); + if (links) { + const { wordLinks, webLinks, fileLinks } = links; + if (wordLinks && wordLinks.length) { + wordResults!.push(...wordLinks.reverse()); + } + if (webLinks && webLinks.length) { + webResults!.push(...webLinks.reverse()); + } + if (fileLinks && fileLinks.length) { + fileResults!.push(...fileLinks.reverse()); + } } } - return links.length > 0 ? links : undefined; + return { wordLinks: wordResults, webLinks: webResults, fileLinks: fileResults }; } detachFromElement(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkQuickpick.ts deleted file mode 100644 index 6e52fe0f8f001..0000000000000 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkQuickpick.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { EventType } from 'vs/base/browser/dom'; -import { localize } from 'vs/nls'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { TerminalLinkProviderType } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; -import { TerminalLinkQuickpickEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ILink } from 'xterm'; - -export class TerminalLinkQuickpick { - constructor( - @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IClipboardService private readonly _clipboardService: IClipboardService - ) { } - - async show(type: TerminalLinkProviderType, links: ILink[]): Promise { - const picks = await this._generatePicks(links); - const options: IPickOptions = { - placeHolder: type === TerminalLinkProviderType.Validated ? localize('terminal.integrated.openValidatedOrProtocolLink', "Select the link to open") : localize('terminal.integrated.copyWordLink', "Select the link to copy"), - }; - if (!picks) { - return; - } - const pick = await this._quickInputService.pick(picks, options); - if (!pick) { - return; - } - if (type === TerminalLinkProviderType.Word) { - this._clipboardService.writeText(pick.label); - } else { - const event = new TerminalLinkQuickpickEvent(EventType.CLICK); - pick.link.activate(event, pick.label); - } - return; - } - - private async _generatePicks(links: ILink[] | undefined): Promise { - if (!links) { - return; - } - const linkKeys: Set = new Set(); - const picks: ITerminalLinkQuickPickItem[] = []; - for (const link of links) { - const label = link.text; - if (!linkKeys.has(label)) { - linkKeys.add(label); - picks.push({ label, link }); - } - } - return picks.length > 0 ? picks : undefined; - } -} - -export interface ITerminalLinkQuickPickItem extends IQuickPickItem { - link: ILink -} - - - From c50372fdddfb055c83e12b50f7d88cd297bfc1df Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 5 Jan 2022 17:30:45 -0600 Subject: [PATCH 1042/2210] get CommandCognisant to work --- src/vs/platform/terminal/node/ptyService.ts | 16 ++++++++++++---- src/vs/platform/terminal/node/terminalProcess.ts | 1 + .../contrib/terminal/browser/terminalInstance.ts | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 26c5980923117..12c5e64179549 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -506,10 +506,10 @@ export class PersistentTerminalProcess extends Disposable { unicodeVersion, reviveBuffer ); - const shellIntegrationAddon = new ShellIntegrationAddon(); - shellIntegrationAddon.onDidStartShellIntegration(() => { + this._serializer.onDidStartShellIntegration(() => { this._terminalProcess.updateProperty(ProcessPropertyType.Capability, ProcessCapability.CommandCognisant); }); + this._fixedDimensions = fixedDimensions; this._orphanQuestionBarrier = null; this._orphanQuestionReplyTime = 0; @@ -755,18 +755,25 @@ class ShellIntegrationAddon extends Disposable implements ITerminalAddon { class XtermSerializer implements ITerminalSerializer { private _xterm: XtermTerminal; private _unicodeAddon?: XtermUnicode11Addon; - + private readonly _onDidStartShellIntegration = new Emitter(); + readonly onDidStartShellIntegration = this._onDidStartShellIntegration.event; constructor( cols: number, rows: number, scrollback: number, unicodeVersion: '6' | '11', - reviveBuffer: string | undefined + reviveBuffer: string | undefined, ) { this._xterm = new XtermTerminal({ cols, rows, scrollback }); if (reviveBuffer) { this._xterm.writeln(reviveBuffer); } + const shellIntegrationAddon = new ShellIntegrationAddon(); + shellIntegrationAddon.activate(this._xterm); + shellIntegrationAddon.onDidStartShellIntegration(() => { + this._onDidStartShellIntegration.fire(); + }); + this._xterm.loadAddon(new ShellIntegrationAddon()); this.setUnicodeVersion(unicodeVersion); } @@ -855,4 +862,5 @@ export interface ITerminalSerializer { handleResize(cols: number, rows: number): void; generateReplayEvent(normalBufferOnly?: boolean): Promise; setUnicodeVersion?(version: '6' | '11'): void; + onDidStartShellIntegration: Event; } diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index d18ced72ea863..0d20ee74f6573 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -431,6 +431,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this.capabilities.push(value as ProcessCapability); } } + this._onDidChangeProperty.fire({ type, value }); } private _startWrite(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 67108f0fbf415..9c043aabd0ed6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1152,6 +1152,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { case ProcessPropertyType.HasChildProcesses: this._onDidChangeHasChildProcesses.fire(value); break; + case ProcessPropertyType.Capability: + this._capabilities.push(value); } }); From c27bbbee66aeed805edf18b8401d10d118fce52a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 5 Jan 2022 17:40:29 -0600 Subject: [PATCH 1043/2210] bunch of errors --- .../contrib/terminal/browser/terminal.ts | 9 ++- .../terminal/browser/terminalActions.ts | 28 +++++++ .../terminal/browser/terminalInstance.ts | 80 ++++++++++++++++--- .../xterm/cognisantCommandTrackerAddon.ts | 10 +++ .../contrib/terminal/common/terminal.ts | 2 + 5 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index b1cae1abbf700..15902714b26e6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -17,7 +17,6 @@ import { DeserializedTerminalEditorInput } from 'vs/workbench/contrib/terminal/b import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; -import { TerminalLinkProviderType } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalEditorService = createDecorator('terminalEditorService'); @@ -791,7 +790,13 @@ export interface ITerminalInstance { /** * Triggers a quick pick that displays links from the viewport of the active terminal. */ - showLinkQuickpick(type: TerminalLinkProviderType): Promise; + showLinkQuickpick(): Promise; + + /** + * Triggers a quick pick that displays recent commands or cwds. Selecting one will + * re-run it in the active terminal. + */ + runRecent(type: 'command' | 'cwd'): Promise; } export interface IXtermTerminal { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index fe3692606f084..dc7efd25f389e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1921,6 +1921,34 @@ export function registerTerminalActions() { accessor.get(ITerminalService).doWithActiveInstance(t => t.showLinkQuickpick(TerminalLinkProviderType.Protocol)); } }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TerminalCommandId.RunRecentCommand, + title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command"), original: 'Run Recent Command' }, + f1: true, + category, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) + }); + } + async run(accessor: ServicesAccessor): Promise { + await accessor.get(ITerminalService).activeInstance?.runRecent('command'); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TerminalCommandId.GoToRecentDirectory, + title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory"), original: 'Go to Recent Directory' }, + f1: true, + category, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) + }); + } + async run(accessor: ServicesAccessor): Promise { + await accessor.get(ITerminalService).activeInstance?.runRecent('cwd'); + } + }); registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9c043aabd0ed6..1f60ac47da193 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,7 +25,7 @@ import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticip import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager, ProcessState, TERMINAL_VIEW_ID, INavigationMode, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, ITerminalProfileResolverService, TerminalCommandId, ITerminalBackend } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; -import { TerminalLinkManager, TerminalLinkProviderType } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; +import { IDetectedLinks, TerminalLinkManager, TerminalLinkProviderType } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstance, ITerminalExternalLinkProvider, IRequestAddInstanceToGroupEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; @@ -660,32 +660,92 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - async showLinkQuickpick(type: TerminalLinkProviderType): Promise { + async showLinkQuickpick(): Promise { if (!this._terminalLinkQuickpick) { this._terminalLinkQuickpick = this._instantiationService.createInstance(TerminalLinkQuickpick); } - const links = await this._getLinks(type); + const links = await this._getLinks(); if (!links) { return; } - return await this._terminalLinkQuickpick.show(type, links); + return await this._terminalLinkQuickpick.show(links); } - private async _getLinks(type: TerminalLinkProviderType): Promise { + private async _getLinks(): Promise { if (!this.areLinksReady || !this._linkManager) { throw new Error('terminal links are not ready, cannot generate link quick pick'); } if (!this.xterm) { throw new Error('no xterm'); } - const links = []; + const wordResults: ILink[] = []; + const webResults: ILink[] = []; + const fileResults: ILink[] = []; + for (let i = this.xterm.raw.buffer.active.length - 1; i >= this.xterm.raw.buffer.active.viewportY; i--) { - const linksForY = await this._linkManager.getLinks(type, i); - if (linksForY) { - links.push(...linksForY); + const links = await this._linkManager.getLinks(i); + if (links) { + const { wordLinks, webLinks, fileLinks } = links; + if (wordLinks && wordLinks.length) { + wordResults!.push(...wordLinks.reverse()); + } + if (webLinks && webLinks.length) { + webResults!.push(...webLinks.reverse()); + } + if (fileLinks && fileLinks.length) { + fileResults!.push(...fileLinks.reverse()); + } + } + } + return { wordLinks: wordResults, webLinks: webResults, fileLinks: fileResults }; + } + + async runRecent(type: 'command' | 'cwd'): Promise { + const commands = this.xterm?.commandTracker.getCommands(); + if (!commands || !this.xterm) { + return; + } + type Item = IQuickPickItem; + const items: Item[] = []; + if (type === 'command') { + for (const { command, timestamp, cwd, exitCode } of commands) { + // trim off /r + const label = command.substring(0, command.length - 1); + if (label.length === 0) { + continue; + } + const cwdDescription = cwd ? `cwd: ${cwd} ` : ''; + const exitCodeDescription = exitCode ? `exitCode: ${exitCode} ` : ''; + items.push({ + label, + description: exitCodeDescription + cwdDescription, + detail: timestamp, + id: timestamp + }); + } + } else { + const cwds = this.xterm.commandTracker.getCommands().map(c => c.cwd).filter(c => c !== undefined && c !== this.cwd); + const map = new Map(); + if (!cwds) { + return; } + for (const cwd of cwds) { + const entry = map.get(cwd!); + if (entry) { + map.set(cwd!, entry + 1); + } else { + map.set(cwd!, 1); + } + } + const sorted = [...map.entries()].sort((a, b) => b[1] - a[1]); + for (const entry of sorted) { + items.push({ label: entry[0] }); + } + } + const result = await this._quickInputService.pick(items.reverse(), {}); + if (result) { + this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, true); } - return links.length > 0 ? links : undefined; } detachFromElement(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts new file mode 100644 index 0000000000000..72b42fd4c8fe9 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; + +export class CognisantCommandTrackerAddon extends CommandTrackerAddon { + +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 5539f748f568b..659e76c622361 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -456,6 +456,8 @@ export const enum TerminalCommandId { ShowWordLinkQuickpick = 'workbench.action.terminal.showWordLinkQuickpick', ShowValidatedLinkQuickpick = 'workbench.action.terminal.showValidatedLinkQuickpick', ShowProtocolLinkQuickpick = 'workbench.action.terminal.showProtocolLinkQuickpick', + RunRecentCommand = 'workbench.action.terminal.runRecentCommand', + GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory', CopySelection = 'workbench.action.terminal.copySelection', SelectAll = 'workbench.action.terminal.selectAll', DeleteWordLeft = 'workbench.action.terminal.deleteWordLeft', From 601a1d1eb7ab89ac281bd0cd8066985ca52f1ca7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 5 Jan 2022 17:42:59 -0600 Subject: [PATCH 1044/2210] fix one more --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 1e226baef359b..1eb67a8a8cf66 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -746,7 +746,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (result) { this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, true); } - return { wordLinks: wordResults, webLinks: webResults, fileLinks: fileResults }; } detachFromElement(): void { From 369aba46b2cec57aaee1a2ac596e120059ab4842 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Wed, 5 Jan 2022 16:12:07 -0800 Subject: [PATCH 1045/2210] Add field for split editor in default settings Adds a new content field for the default settings without the most commonly used group. The field is then consumed by the default setting side of the split json editor. Fixes #137122 --- .../preferences/browser/preferencesService.ts | 7 ++++--- .../preferences/common/preferencesModels.ts | 20 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 6c2972c6d3470..0f72ad2383d0c 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -120,7 +120,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic resolveModel(uri: URI): ITextModel | null { if (this.isDefaultSettingsResource(uri)) { - + // We opened a split json editor in this case, + // and this half shows the default settings. const target = this.getConfigurationTargetFromDefaultSettingsResource(uri); const languageSelection = this.languageService.createById('jsonc'); const model = this._register(this.modelService.createModel('', languageSelection, uri)); @@ -134,7 +135,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return; } defaultSettings = this.getDefaultSettings(target); - this.modelService.updateModel(model, defaultSettings.getContent(true)); + this.modelService.updateModel(model, defaultSettings.getContentWithoutMostCommonlyUsed(true)); defaultSettings._onDidChange.fire(); } }); @@ -142,7 +143,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic // Check if Default settings is already created and updated in above promise if (!defaultSettings) { defaultSettings = this.getDefaultSettings(target); - this.modelService.updateModel(model, defaultSettings.getContent(true)); + this.modelService.updateModel(model, defaultSettings.getContentWithoutMostCommonlyUsed(true)); } return model; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 139be78f14527..a6d281a404bc7 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -461,6 +461,7 @@ export class DefaultSettings extends Disposable { private _allSettingsGroups: ISettingsGroup[] | undefined; private _content: string | undefined; + private _contentWithoutMostCommonlyUsed: string | undefined; private _settingsByName = new Map(); readonly _onDidChange: Emitter = this._register(new Emitter()); @@ -481,6 +482,14 @@ export class DefaultSettings extends Disposable { return this._content!; } + getContentWithoutMostCommonlyUsed(forceUpdate = false): string { + if (!this._contentWithoutMostCommonlyUsed || forceUpdate) { + this.initialize(); + } + + return this._contentWithoutMostCommonlyUsed!; + } + getSettingsGroups(forceUpdate = false): ISettingsGroup[] { if (!this._allSettingsGroups || forceUpdate) { this.initialize(); @@ -491,7 +500,8 @@ export class DefaultSettings extends Disposable { private initialize(): void { this._allSettingsGroups = this.parse(); - this._content = this.toContent(this._allSettingsGroups); + this._content = this.toContent(this._allSettingsGroups, 0); + this._contentWithoutMostCommonlyUsed = this.toContent(this._allSettingsGroups, 1); } private parse(): ISettingsGroup[] { @@ -738,11 +748,11 @@ export class DefaultSettings extends Disposable { return c1.order - c2.order; } - private toContent(settingsGroups: ISettingsGroup[]): string { + private toContent(settingsGroups: ISettingsGroup[], startIndex: number): string { const builder = new SettingsContentBuilder(); - settingsGroups.forEach((settingsGroup, i) => { - builder.pushGroup(settingsGroup, i === 0, i === settingsGroups.length - 1); - }); + for (let i = startIndex; i < settingsGroups.length - 1; i++) { + builder.pushGroup(settingsGroups[i], i === startIndex, i === settingsGroups.length - 1); + } return builder.getContent(); } From cdcc51d52106fe9412caf5ab0d1da812c3a06f72 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Wed, 5 Jan 2022 16:48:08 -0800 Subject: [PATCH 1046/2210] Revert "Add field for split editor in default settings" This reverts commit 369aba46b2cec57aaee1a2ac596e120059ab4842. --- .../preferences/browser/preferencesService.ts | 7 +++---- .../preferences/common/preferencesModels.ts | 20 +++++-------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 0f72ad2383d0c..6c2972c6d3470 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -120,8 +120,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic resolveModel(uri: URI): ITextModel | null { if (this.isDefaultSettingsResource(uri)) { - // We opened a split json editor in this case, - // and this half shows the default settings. + const target = this.getConfigurationTargetFromDefaultSettingsResource(uri); const languageSelection = this.languageService.createById('jsonc'); const model = this._register(this.modelService.createModel('', languageSelection, uri)); @@ -135,7 +134,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return; } defaultSettings = this.getDefaultSettings(target); - this.modelService.updateModel(model, defaultSettings.getContentWithoutMostCommonlyUsed(true)); + this.modelService.updateModel(model, defaultSettings.getContent(true)); defaultSettings._onDidChange.fire(); } }); @@ -143,7 +142,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic // Check if Default settings is already created and updated in above promise if (!defaultSettings) { defaultSettings = this.getDefaultSettings(target); - this.modelService.updateModel(model, defaultSettings.getContentWithoutMostCommonlyUsed(true)); + this.modelService.updateModel(model, defaultSettings.getContent(true)); } return model; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index a6d281a404bc7..139be78f14527 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -461,7 +461,6 @@ export class DefaultSettings extends Disposable { private _allSettingsGroups: ISettingsGroup[] | undefined; private _content: string | undefined; - private _contentWithoutMostCommonlyUsed: string | undefined; private _settingsByName = new Map(); readonly _onDidChange: Emitter = this._register(new Emitter()); @@ -482,14 +481,6 @@ export class DefaultSettings extends Disposable { return this._content!; } - getContentWithoutMostCommonlyUsed(forceUpdate = false): string { - if (!this._contentWithoutMostCommonlyUsed || forceUpdate) { - this.initialize(); - } - - return this._contentWithoutMostCommonlyUsed!; - } - getSettingsGroups(forceUpdate = false): ISettingsGroup[] { if (!this._allSettingsGroups || forceUpdate) { this.initialize(); @@ -500,8 +491,7 @@ export class DefaultSettings extends Disposable { private initialize(): void { this._allSettingsGroups = this.parse(); - this._content = this.toContent(this._allSettingsGroups, 0); - this._contentWithoutMostCommonlyUsed = this.toContent(this._allSettingsGroups, 1); + this._content = this.toContent(this._allSettingsGroups); } private parse(): ISettingsGroup[] { @@ -748,11 +738,11 @@ export class DefaultSettings extends Disposable { return c1.order - c2.order; } - private toContent(settingsGroups: ISettingsGroup[], startIndex: number): string { + private toContent(settingsGroups: ISettingsGroup[]): string { const builder = new SettingsContentBuilder(); - for (let i = startIndex; i < settingsGroups.length - 1; i++) { - builder.pushGroup(settingsGroups[i], i === startIndex, i === settingsGroups.length - 1); - } + settingsGroups.forEach((settingsGroup, i) => { + builder.pushGroup(settingsGroup, i === 0, i === settingsGroups.length - 1); + }); return builder.getContent(); } From a2b315561aa3551c41b59e3019eabfeb47b9cb1d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 5 Jan 2022 18:52:11 -0600 Subject: [PATCH 1047/2210] hook it together --- src/vs/platform/terminal/common/terminal.ts | 22 ++++++ .../terminal/browser/terminalInstance.ts | 2 +- .../xterm/cognisantCommandTrackerAddon.ts | 70 +++++++++++++++++++ .../browser/xterm/commandTrackerAddon.ts | 21 ++++-- .../terminal/browser/xterm/xtermTerminal.ts | 56 ++++++++++++++- .../contrib/terminal/common/terminal.ts | 4 +- 6 files changed, 167 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 071239dda238f..83635ea4ea2f5 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -372,6 +372,28 @@ export interface IHeartbeatService { readonly onBeat: Event; } +export interface TerminalCommand { + command: string; + timestamp: string; + cwd?: string; + exitCode?: number; +} + +export enum ShellIntegrationInfo { + RemoteHost = 'RemoteHost', + CurrentDir = 'CurrentDir', +} + +export enum ShellIntegrationInteraction { + PromptStart = 'PROMPT_START', + CommandStart = 'COMMAND_START', + CommandExecuted = 'COMMAND_EXECUTED', + CommandFinished = 'COMMAND_FINISHED' +} + + +export interface IShellChangeEvent { type: ShellIntegrationInfo | ShellIntegrationInteraction, value: string } + export interface IShellLaunchConfig { /** * The name of the terminal, if this is not set the name of the process will be used. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 1eb67a8a8cf66..a4e88d06eacb7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -566,7 +566,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { throw new Error('Terminal disposed of during xterm.js creation'); } - const xterm = this._instantiationService.createInstance(XtermTerminal, Terminal, this._configHelper, this._cols, this._rows, this.target || TerminalLocation.Panel); + const xterm = this._instantiationService.createInstance(XtermTerminal, Terminal, this._configHelper, this._cols, this._rows, this.target || TerminalLocation.Panel, this.capabilities); this.xterm = xterm; const lineDataEventAddon = new LineDataEventAddon(); this.xterm.raw.loadAddon(lineDataEventAddon); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts index 72b42fd4c8fe9..01c7cda9c42af 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -3,8 +3,78 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Emitter } from 'vs/base/common/event'; +import { ProcessCapability, ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; +import { Terminal } from 'xterm'; export class CognisantCommandTrackerAddon extends CommandTrackerAddon { + private _capabilities: ProcessCapability[] | undefined = undefined; + private _dataIsCommand = false; + private _commands: TerminalCommand[] = []; + private _exitCode: number | undefined; + private _cwd: string | undefined; + private _currentCommand = ''; + private readonly _onCwdChanged = new Emitter(); + readonly onCwdChanged = this._onCwdChanged.event; + + override activate(terminal: Terminal): void { + terminal.onData(data => { + if (this._shellIntegrationEnabled() && this._dataIsCommand) { + this._currentCommand += data; + } + }); + } + + private _shellIntegrationEnabled(): boolean { + return this._capabilities?.includes(ProcessCapability.CommandCognisant) || false; + } + + override handleIntegratedShellChange(event: { type: string, value: string }): void { + if (!this._shellIntegrationEnabled()) { + return; + } + switch (event.type) { + case ShellIntegrationInfo.CurrentDir: + this._cwd = event.value; + this._onCwdChanged.fire(this._cwd); + break; + case ShellIntegrationInfo.RemoteHost: + break; + case ShellIntegrationInteraction.PromptStart: + break; + case ShellIntegrationInteraction.CommandStart: + this._dataIsCommand = true; + break; + case ShellIntegrationInteraction.CommandExecuted: + break; + case ShellIntegrationInteraction.CommandFinished: + this._exitCode = Number.parseInt(event.value); + if (!this._currentCommand.startsWith('\\') && this._currentCommand !== '') { + this._commands.push( + { + command: this._currentCommand, + timestamp: this._getCurrentTimestamp(), + cwd: this._cwd, + exitCode: this._exitCode + }); + } + this._currentCommand = ''; + break; + default: + return; + } + } + + private _getCurrentTimestamp(): string { + const toTwoDigits = (v: number) => v < 10 ? `0${v}` : v; + const toThreeDigits = (v: number) => v < 10 ? `00${v}` : v < 100 ? `0${v}` : v; + const currentTime = new Date(); + return `${currentTime.getFullYear()}-${toTwoDigits(currentTime.getMonth() + 1)}-${toTwoDigits(currentTime.getDate())} ${toTwoDigits(currentTime.getHours())}:${toTwoDigits(currentTime.getMinutes())}:${toTwoDigits(currentTime.getSeconds())}.${toThreeDigits(currentTime.getMilliseconds())}`; + } + + override getCommands(): TerminalCommand[] { + return this._commands; + } } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index aa08c6581009f..f32c01ed57ecb 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -5,6 +5,7 @@ import type { Terminal, IMarker, ITerminalAddon } from 'xterm'; import { ICommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalCommand } from 'vs/platform/terminal/common/terminal'; /** * The minimum size of the prompt in which to assume the line is a command. @@ -27,6 +28,10 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { private _isDisposable: boolean = false; private _terminal: Terminal | undefined; + getCommands(): TerminalCommand[] { + return []; + } + activate(terminal: Terminal): void { this._terminal = terminal; terminal.onKey(e => this._onKey(e.key)); @@ -35,15 +40,23 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { dispose(): void { } + clearMarker(): void { + // Clear the current marker so successive focus/selection actions are performed from the + // bottom of the buffer + this._currentMarker = Boundary.Bottom; + this._selectionStart = null; + } + + handleIntegratedShellChange(event: { type: string, value: string }): void { + + } + private _onKey(key: string): void { if (key === '\x0d') { this._onEnter(); } - // Clear the current marker so successive focus/selection actions are performed from the - // bottom of the buffer - this._currentMarker = Boundary.Bottom; - this._selectionStart = null; + this.clearMarker(); } private _onEnter(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 1f77f27ccd379..259ff77fd84b2 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -12,7 +12,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ProcessCapability, ShellIntegrationInfo, ShellIntegrationInteraction, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ICommandTracker, ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -28,6 +28,7 @@ import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TERMINAL_FOREGROUND_COLOR, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR, ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { Color } from 'vs/base/common/color'; +import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -76,6 +77,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { cols: number, rows: number, location: TerminalLocation, + capabilities: ProcessCapability[], @IConfigurationService private readonly _configurationService: IConfigurationService, @ILogService private readonly _logService: ILogService, @INotificationService private readonly _notificationService: INotificationService, @@ -118,6 +120,8 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { })); this._core = (this.raw as any)._core as IXtermCore; + this.raw.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); + this.raw.parser.registerOscHandler(1337, (data => this._updateCwd(data))); this.add(this._configurationService.onDidChangeConfiguration(async e => { if (e.affectsConfiguration(TerminalSettingId.GpuAcceleration)) { XtermTerminal._suggestedRendererType = undefined; @@ -140,10 +144,58 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Load addons this._updateUnicodeVersion(); - this._commandTrackerAddon = new CommandTrackerAddon(); + this._commandTrackerAddon = capabilities.find(c => c === ProcessCapability.CommandCognisant) ? new CognisantCommandTrackerAddon() : new CommandTrackerAddon(); this.raw.loadAddon(this._commandTrackerAddon); } + private _updateCwd(data: string): boolean { + let value: string | undefined; + const [type, info] = data.split('='); + switch (type) { + case ShellIntegrationInfo.CurrentDir: + value = info; + break; + default: + return false; + } + if (!value) { + return false; + } + this._commandTrackerAddon.handleIntegratedShellChange({ type, value }); + return true; + } + + private _handleShellIntegration(data: string): boolean { + let type: ShellIntegrationInteraction | undefined; + const [command, exitCode] = data.split(';'); + switch (command) { + case 'A': + type = ShellIntegrationInteraction.PromptStart; + break; + case 'B': + type = ShellIntegrationInteraction.CommandStart; + break; + case 'C': + type = ShellIntegrationInteraction.CommandExecuted; + break; + case 'D': + type = ShellIntegrationInteraction.CommandFinished; + if (this.raw.buffer.active.cursorX >= 2) { + this.raw?.registerMarker(0); + this.commandTracker.clearMarker(); + } + break; + default: + return false; + } + const value = exitCode || type; + if (!value) { + return false; + } + this._commandTrackerAddon.handleIntegratedShellChange({ type, value }); + return true; + } + attachToElement(container: HTMLElement) { // Update the theme when attaching as the terminal location could have changed this._updateTheme(); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 659e76c622361..83f99c2d1d7e8 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, TerminalCommand } from 'vs/platform/terminal/common/terminal'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; @@ -326,6 +326,8 @@ export interface ICommandTracker { selectToNextCommand(): void; selectToPreviousLine(): void; selectToNextLine(): void; + getCommands(): TerminalCommand[]; + clearMarker(): void; } export interface INavigationMode { From 9afbea7caab9dd06592ddd98aa4161b43b126d3f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 5 Jan 2022 17:51:19 -0800 Subject: [PATCH 1048/2210] Fix loader for jupyter notebooks --- .../view/renderers/backLayerWebView.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 609d6eb83989e..5b8d2496b2dea 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -428,7 +428,6 @@ export class BackLayerWebView extends Disposable { resolveFunc = resolve; }); - if (!isWeb) { const loaderUri = FileAccess.asFileUri('vs/loader.js', require); const loader = this.asWebviewUri(loaderUri, undefined); @@ -476,6 +475,22 @@ var requirejs = (function() { await this._initialized; } + private getBuiltinLocalResourceRoots(): URI[] { + // Python notebooks assume that requirejs is a global. + // For all other notebooks, they need to provide their own loader. + if (!this.documentUri.path.toLowerCase().endsWith('.ipynb')) { + return []; + } + + if (isWeb) { + return []; // script is inlined + } + + return [ + dirname(FileAccess.asFileUri('vs/loader.js', require)), + ]; + } + private _initialize(content: string) { if (!document.body.contains(this.element)) { throw new Error('Element is already detached from the DOM tree'); @@ -842,8 +857,8 @@ var requirejs = (function() { ...this.notebookService.getNotebookProviderResourceRoots(), ...this.notebookService.getRenderers().map(x => dirname(x.entrypoint)), ...workspaceFolders, + ...this.getBuiltinLocalResourceRoots(), ]; - const webview = webviewService.createWebviewElement(this.id, { purpose: WebviewContentPurpose.NotebookRenderer, enableFindWidget: false, From e4e1eb75e90cdd395e04993bb0d36dd5285cf466 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 5 Jan 2022 21:22:04 -0600 Subject: [PATCH 1049/2210] get it to work --- .../contrib/terminal/browser/terminal.ts | 5 ++++ .../terminal/browser/terminalActions.ts | 28 +++++++++++++++++++ .../terminal/browser/terminalInstance.ts | 5 +++- .../xterm/cognisantCommandTrackerAddon.ts | 12 ++------ .../terminal/browser/xterm/xtermTerminal.ts | 11 ++++++-- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 92804dcb2d9f9..08f0da1045d47 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -850,6 +850,11 @@ export interface IXtermTerminal { * Enables the webgl renderer */ enableWebglRenderer(): Promise + + /* + * When process capabilites are updated, update the command tracker + */ + upgradeCommandTracker(): void; } export interface IRequestAddInstanceToGroupEvent { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 99ddb4fba685b..ba03c5b035260 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -300,6 +300,34 @@ export function registerTerminalActions() { await terminalGroupService.showPanel(true); } }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TerminalCommandId.RunRecentCommand, + title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command"), original: 'Run Recent Command' }, + f1: true, + category, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) + }); + } + async run(accessor: ServicesAccessor): Promise { + await accessor.get(ITerminalService).activeInstance?.runRecent('command'); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TerminalCommandId.GoToRecentDirectory, + title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory"), original: 'Go to Recent Directory' }, + f1: true, + category, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) + }); + } + async run(accessor: ServicesAccessor): Promise { + await accessor.get(ITerminalService).activeInstance?.runRecent('cwd'); + } + }); registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index a4e88d06eacb7..66384a8baeaea 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -68,6 +68,7 @@ import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnviro import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { isFirefox, isSafari } from 'vs/base/browser/browser'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; +import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon'; const enum Constants { /** @@ -1213,7 +1214,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._onDidChangeHasChildProcesses.fire(value); break; case ProcessPropertyType.Capability: - this._capabilities.push(value); + if (value === ProcessCapability.CommandCognisant && !(this.xterm?.commandTracker instanceof CognisantCommandTrackerAddon)) { + this.xterm?.upgradeCommandTracker(); + } } }); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts index 01c7cda9c42af..5a463f5964753 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; -import { ProcessCapability, ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; +import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { Terminal } from 'xterm'; export class CognisantCommandTrackerAddon extends CommandTrackerAddon { - private _capabilities: ProcessCapability[] | undefined = undefined; private _dataIsCommand = false; private _commands: TerminalCommand[] = []; private _exitCode: number | undefined; @@ -21,20 +20,13 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { override activate(terminal: Terminal): void { terminal.onData(data => { - if (this._shellIntegrationEnabled() && this._dataIsCommand) { + if (this._dataIsCommand) { this._currentCommand += data; } }); } - private _shellIntegrationEnabled(): boolean { - return this._capabilities?.includes(ProcessCapability.CommandCognisant) || false; - } - override handleIntegratedShellChange(event: { type: string, value: string }): void { - if (!this._shellIntegrationEnabled()) { - return; - } switch (event.type) { case ShellIntegrationInfo.CurrentDir: this._cwd = event.value; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 259ff77fd84b2..93a8749eeaf2d 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -143,11 +143,18 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Load addons this._updateUnicodeVersion(); - - this._commandTrackerAddon = capabilities.find(c => c === ProcessCapability.CommandCognisant) ? new CognisantCommandTrackerAddon() : new CommandTrackerAddon(); + this._commandTrackerAddon = new CommandTrackerAddon(); this.raw.loadAddon(this._commandTrackerAddon); } + upgradeCommandTracker(): void { + this.raw.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); + this.raw.parser.registerOscHandler(1337, (data => this._updateCwd(data))); + this._commandTrackerAddon.dispose(); + this._commandTrackerAddon = new CognisantCommandTrackerAddon(); + this._commandTrackerAddon.activate(this.raw); + } + private _updateCwd(data: string): boolean { let value: string | undefined; const [type, info] = data.split('='); From 8cd17ba76e65fd71a143417da01e112285a792f1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 5 Jan 2022 22:01:50 -0600 Subject: [PATCH 1050/2210] make time/date less verbose --- .../terminal/browser/terminalInstance.ts | 3 +- .../contrib/terminal/browser/terminalTime.ts | 31 +++++++++++++++++++ .../xterm/cognisantCommandTrackerAddon.ts | 10 ++---- 3 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalTime.ts diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 66384a8baeaea..2c098699d28b3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -69,6 +69,7 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work import { isFirefox, isSafari } from 'vs/base/browser/browser'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon'; +import { getTimeSinceCommand } from 'vs/workbench/contrib/terminal/browser/terminalTime'; const enum Constants { /** @@ -720,7 +721,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { items.push({ label, description: exitCodeDescription + cwdDescription, - detail: timestamp, + detail: getTimeSinceCommand(timestamp), id: timestamp }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTime.ts b/src/vs/workbench/contrib/terminal/browser/terminalTime.ts new file mode 100644 index 0000000000000..20994e59789eb --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalTime.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function getCurrentTimestamp(): string { + const currentTime = new Date(); + return `${currentTime.getMonth() + 1}-${currentTime.getDate()}-${currentTime.getHours()}-${currentTime.getMinutes()}-${currentTime.getSeconds()}-${currentTime.getMilliseconds()} `; +} +export function getTimeSinceCommand(timeOfCommand: string): string { + const timeNow = getCurrentTimestamp(); + const now = timeNow.split('-'); + const command = timeOfCommand.split('-'); + let i = 0; + while (now[i] === command[i] && i < command.length) { + i++; + } + switch (i) { + case 0: + return `${i} months ago`; + case 1: + return `${i} days ago`; + case 2: + return `${i} hours ago`; + case 3: + return `${i} minutes ago`; + case 4: + return `${i} seconds ago`; + } + return 'a long time ago'; +} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts index 5a463f5964753..f4ab0def0b6f9 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -5,6 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; +import { getCurrentTimestamp } from 'vs/workbench/contrib/terminal/browser/terminalTime'; import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { Terminal } from 'xterm'; @@ -47,7 +48,7 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { this._commands.push( { command: this._currentCommand, - timestamp: this._getCurrentTimestamp(), + timestamp: getCurrentTimestamp(), cwd: this._cwd, exitCode: this._exitCode }); @@ -59,13 +60,6 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { } } - private _getCurrentTimestamp(): string { - const toTwoDigits = (v: number) => v < 10 ? `0${v}` : v; - const toThreeDigits = (v: number) => v < 10 ? `00${v}` : v < 100 ? `0${v}` : v; - const currentTime = new Date(); - return `${currentTime.getFullYear()}-${toTwoDigits(currentTime.getMonth() + 1)}-${toTwoDigits(currentTime.getDate())} ${toTwoDigits(currentTime.getHours())}:${toTwoDigits(currentTime.getMinutes())}:${toTwoDigits(currentTime.getSeconds())}.${toThreeDigits(currentTime.getMilliseconds())}`; - } - override getCommands(): TerminalCommand[] { return this._commands; } From 8c25da86a7669b5ddb5a601731cc5f6034ed4d18 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 5 Jan 2022 22:11:57 -0600 Subject: [PATCH 1051/2210] fix timeline --- .../contrib/terminal/browser/terminalTime.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTime.ts b/src/vs/workbench/contrib/terminal/browser/terminalTime.ts index 20994e59789eb..dd41031c5f0a1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTime.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTime.ts @@ -15,17 +15,18 @@ export function getTimeSinceCommand(timeOfCommand: string): string { while (now[i] === command[i] && i < command.length) { i++; } + const amount = Number.parseInt(now[i]) - Number.parseInt(command[i]); switch (i) { case 0: - return `${i} months ago`; + return `${amount} months ago`; case 1: - return `${i} days ago`; + return `${amount} days ago`; case 2: - return `${i} hours ago`; + return `${amount} hours ago`; case 3: - return `${i} minutes ago`; + return `${amount} minutes ago`; case 4: - return `${i} seconds ago`; + return `${amount} seconds ago`; } return 'a long time ago'; } From ef01b87295a6ae2d171d4cd563aa9dfed6e0fa44 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 6 Jan 2022 07:42:03 +0100 Subject: [PATCH 1052/2210] tests - `createRandomIPCHandle` is flaky --- src/vs/base/parts/ipc/test/node/ipc.net.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 50c3d1428d934..2f3f3774cba92 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -13,6 +13,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import { flakySuite } from 'vs/base/test/common/testUtils'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import product from 'vs/platform/product/common/product'; @@ -537,7 +538,7 @@ suite('PersistentProtocol reconnection', () => { }); }); -suite('IPC, create handle', () => { +flakySuite('IPC, create handle', () => { test('createRandomIPCHandle', async () => { return testIPCHandle(createRandomIPCHandle()); From d622a8d39fa45826d43208e94894ea45126bede4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 6 Jan 2022 07:45:19 +0100 Subject: [PATCH 1053/2210] tests - skip flakes (#140200, #140201) --- .../vscode-api-tests/src/singlefolder-tests/notebook.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 2a6df581c3372..2ff854913ef7e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -190,7 +190,7 @@ suite('Notebook API tests', function () { assert.strictEqual(selectionRedo[0].end, 2); }); - test('editor editing event', async function () { + test.skip('editor editing event', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/140200 const notebook = await openRandomNotebookDocument(); const editor = await vscode.window.showNotebookDocument(notebook); @@ -311,7 +311,7 @@ suite('Notebook API tests', function () { assert.strictEqual(secondCell.executionSummary?.success, true); }); - test('notebook cell actions', async function () { + test.skip('notebook cell actions', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/140201 const notebook = await openRandomNotebookDocument(); const editor = await vscode.window.showNotebookDocument(notebook); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); From cac59bca79a4c0af332d67ebd40f2e78c66179b5 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 6 Jan 2022 09:29:58 +0100 Subject: [PATCH 1054/2210] group server cli arguments (For #137658) --- src/vs/server/serverEnvironmentService.ts | 65 ++++++++++++++--------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/vs/server/serverEnvironmentService.ts b/src/vs/server/serverEnvironmentService.ts index d670ecf75e77b..aaebbd4922f0c 100644 --- a/src/vs/server/serverEnvironmentService.ts +++ b/src/vs/server/serverEnvironmentService.ts @@ -61,8 +61,14 @@ export const serverOptions: OptionDescriptions = { }; export interface ServerParsedArgs { + + /* ----- server setup ----- */ + + host?: string; port?: string; 'pick-port'?: string; + 'socket-path'?: string; + /** * A secret token that must be provided by the web client with all requests. * Use only `[0-9A-Za-z\-]`. @@ -84,21 +90,39 @@ export interface ServerParsedArgs { * This secret must be communicated to any vscode instance via the resolver or embedder API. */ 'connection-secret'?: string; - host?: string; - 'socket-path'?: string; - driver?: string; + + 'disable-websocket-compression'?: boolean; + 'print-startup-performance'?: boolean; 'print-ip-address'?: boolean; - 'disable-websocket-compression'?: boolean; + + 'accept-server-license-terms': boolean; + + /* ----- vs code options ----- */ + + 'user-data-dir'?: string; + + driver?: string; + 'disable-telemetry'?: boolean; fileWatcherPolling?: string; - 'start-server'?: boolean; - 'enable-remote-auto-shutdown'?: boolean; - 'remote-auto-shutdown-without-delay'?: boolean; + 'log'?: string; + 'logsPath'?: string; + + 'force-disable-user-env'?: boolean; + + /* ----- vs code web options ----- */ + workspace: string; + folder: string; + 'enable-sync'?: boolean; + 'github-auth'?: string; + + /* ----- extension management ----- */ 'extensions-dir'?: string; 'extensions-download-dir'?: string; + 'builtin-extensions-dir'?: string; 'install-extension'?: string[]; 'install-builtin-extension'?: string[]; 'uninstall-extension'?: string[]; @@ -106,33 +130,24 @@ export interface ServerParsedArgs { 'locate-extension'?: string[]; 'show-versions'?: boolean; 'category'?: string; - - 'force-disable-user-env'?: boolean; - 'use-host-proxy'?: string; - - 'without-browser-env-var'?: boolean; - force?: boolean; // used by install-extension 'do-not-sync'?: boolean; // used by install-extension 'pre-release'?: boolean; // used by install-extension - 'user-data-dir'?: string; - 'builtin-extensions-dir'?: string; + 'start-server'?: boolean; - // web - workspace: string; - folder: string; - 'enable-sync'?: boolean; - 'github-auth'?: string; - 'log'?: string; - 'logsPath'?: string; + /* ----- remote development options ----- */ + + 'enable-remote-auto-shutdown'?: boolean; + 'remote-auto-shutdown-without-delay'?: boolean; - // server cli + 'use-host-proxy'?: string; + 'without-browser-env-var'?: boolean; + + /* ----- server cli ----- */ help: boolean; version: boolean; - 'accept-server-license-terms': boolean; - _: string[]; } From 1bcc4d63d40d98f2bc7670fa6d7a9bc48793f43b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 6 Jan 2022 10:59:42 +0100 Subject: [PATCH 1055/2210] watcher - skip excludes when watching file directly --- src/vs/base/common/glob.ts | 4 ++-- .../files/node/watcher/nodejs/nodejsWatcher.ts | 12 ++++++------ .../files/test/node/nodejsWatcher.integrationTest.ts | 11 +++++++++-- src/vscode-dts/vscode.d.ts | 6 +++--- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index b7e72d76fc35e..c952085774043 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -27,8 +27,8 @@ export interface SiblingClause { when: string; } -const GLOBSTAR = '**'; -const GLOB_SPLIT = '/'; +export const GLOBSTAR = '**'; +export const GLOB_SPLIT = '/'; const PATH_REGEX = '[/\\\\]'; // any slash or backslash const NO_PATH_REGEX = '[^/\\\\]'; // any non-slash and non-backslash const ALL_FORWARD_SLASHES = /\//g; diff --git a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts index cf99f96381e80..e51a42c7d6b40 100644 --- a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts @@ -303,14 +303,14 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche // File still exists, so emit as change event and reapply the watcher if (fileExists) { - this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }); + this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }, true /* skip excludes (file is explicitly watched) */); disposables.add(await this.doWatch(path, false)); } // File seems to be really gone, so emit a deleted event and dispose else { - const eventPromise = this.onFileChange({ path: this.request.path, type: FileChangeType.DELETED }); + const eventPromise = this.onFileChange({ path: this.request.path, type: FileChangeType.DELETED }, true /* skip excludes (file is explicitly watched) */); // Important to await the event delivery // before disposing the watcher, otherwise @@ -328,7 +328,7 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche // File changed else { - this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }); + this.onFileChange({ path: this.request.path, type: FileChangeType.UPDATED }, true /* skip excludes (file is explicitly watched) */); } } }); @@ -344,7 +344,7 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche }); } - private async onFileChange(event: IDiskFileChange): Promise { + private async onFileChange(event: IDiskFileChange, skipExcludes = false): Promise { if (this.cts.token.isCancellationRequested) { return; } @@ -354,8 +354,8 @@ export class NodeJSFileWatcher extends Disposable implements INonRecursiveWatche this.trace(`${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); } - // Add to buffer unless ignored - if (this.excludes.some(exclude => exclude(event.path))) { + // Add to buffer unless ignored (not if explicitly disabled) + if (!skipExcludes && this.excludes.some(exclude => exclude(event.path))) { if (this.verboseLogging) { this.trace(` >> ignored ${event.path}`); } diff --git a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts index b8e596f45a2ac..fa688171bc37a 100644 --- a/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts @@ -58,7 +58,7 @@ import { DeferredPromise } from 'vs/base/common/async'; await createWatcher(testDir); }); - function createWatcher(path: string): Promise { + function createWatcher(path: string, excludes: string[] = []): Promise { if (watcher) { watcher.dispose(); } @@ -66,7 +66,7 @@ import { DeferredPromise } from 'vs/base/common/async'; const emitter = new Emitter(); event = emitter.event; - watcher = new TestNodeJSFileWatcher({ path, excludes: [] }, changes => emitter.fire(changes), msg => { + watcher = new TestNodeJSFileWatcher({ path, excludes }, changes => emitter.fire(changes), msg => { if (loggingEnabled) { console.log(`[recursive watcher test message] ${msg.type}: ${msg.message}`); } @@ -344,6 +344,13 @@ import { DeferredPromise } from 'vs/base/common/async'; await Promise.all([changeFuture1]); }); + test('excludes are ignored (file watch)', async function () { + const filePath = join(testDir, 'lorem.txt'); + await createWatcher(filePath, ['**']); + + return basicCrudTest(filePath, true); + }); + (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (folder watch)', async function () { const link = join(testDir, 'deep-linked'); const linkTarget = join(testDir, 'deep'); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 0892a899370e2..4844f4d707150 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -7402,9 +7402,9 @@ declare module 'vscode' { readonly onDidChangeFile: Event; /** - * Subscribe to file change events in the file or folder denoted by `uri`. For folders, + * Subscribes to file change events in the file or folder denoted by `uri`. For folders, * the option `recursive` indicates whether subfolders, sub-subfolders, etc. should - * be watched for file changes as well. When `recursive: false`, only changes to the + * be watched for file changes as well. With `recursive: false`, only changes to the * files that are direct children of the folder should trigger an event. * * The `excludes` array is used to indicate paths that should be excluded from file @@ -7412,7 +7412,7 @@ declare module 'vscode' { * is configurable by the user. Each entry can be be: * - the absolute path to exclude * - a relative path to exclude (for example `build/output`) - * - a simple glob pattern (for example `**\build`, `output/**`) + * - a simple glob pattern (for example `**​/build`, `output/**`) * * It is the file system provider's job to call {@linkcode FileSystemProvider.onDidChangeFile onDidChangeFile} * for every change given these rules. No event should be emitted for files that match any of the provided From faf4de0d7ac5b3237babd0e84d62e2b24ca33748 Mon Sep 17 00:00:00 2001 From: John Murray Date: Thu, 6 Jan 2022 12:55:48 +0000 Subject: [PATCH 1056/2210] fix #118617 use term 'remote' when referring to lack of upstream branch (#120467) --- extensions/git/src/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 7d230790b0ff2..3629e2d89cfc9 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2136,7 +2136,7 @@ export class CommandCenter { } const branchName = repository.HEAD.name; - const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName); + const message = localize('confirm publish branch', "The branch '{0}' has no remote branch. Would you like to publish this branch?", branchName); const yes = localize('ok', "OK"); const pick = await window.showWarningMessage(message, { modal: true }, yes); @@ -2286,7 +2286,7 @@ export class CommandCenter { return; } else if (!HEAD.upstream) { const branchName = HEAD.name; - const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName); + const message = localize('confirm publish branch', "The branch '{0}' has no remote branch. Would you like to publish this branch?", branchName); const yes = localize('ok', "OK"); const pick = await window.showWarningMessage(message, { modal: true }, yes); From 6cd86c023a3e79069e0caff43ede16d278e40892 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 6 Jan 2022 14:23:08 +0100 Subject: [PATCH 1057/2210] fix issue that causes inlay hints to get lost when using multiple editors, fyi @bpasero --- src/vs/editor/browser/editorBrowser.ts | 5 +++++ src/vs/editor/browser/widget/codeEditorWidget.ts | 7 +++++++ .../editor/contrib/inlayHints/inlayHintsController.ts | 10 ++++++---- src/vs/monaco.d.ts | 4 ++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 404d8beac00c3..cf5b1e12502c3 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -746,6 +746,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ getLineDecorations(lineNumber: number): IModelDecoration[] | null; + /** + * Get all the decorations for a range (filtering out decorations from other editors). + */ + getDecorationsInRange(range: Range): IModelDecoration[] | null + /** * All decorations added through this call will get the ownerId of this editor. * @see {@link ITextModel.deltaDecorations} diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 0ddf9d7280398..5577f6db98f1c 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1220,6 +1220,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.model.getLineDecorations(lineNumber, this._id, filterValidationDecorations(this._configuration.options)); } + public getDecorationsInRange(range: Range): IModelDecoration[] | null { + if (!this._modelData) { + return null; + } + return this._modelData.model.getDecorationsInRange(range, this._id, filterValidationDecorations(this._configuration.options)); + } + public deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { if (!this._modelData) { return []; diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index e84e2e18f4e35..f8410ce022440 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -111,8 +111,9 @@ export class InlayHintsController implements IEditorContribution { static readonly ID: string = 'editor.contrib.InlayHints'; - private static _decorationOwnerIdPool = 0; - private readonly _decorationOwnerId = ++InlayHintsController._decorationOwnerIdPool; + static get(editor: ICodeEditor) { + return editor.getContribution(InlayHintsController.ID) ?? undefined; + } private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); @@ -368,7 +369,8 @@ export class InlayHintsController implements IEditorContribution { // and only update those decorations const decorationIdsToReplace: string[] = []; for (const range of ranges) { - for (const { id } of model.getDecorationsInRange(range, this._decorationOwnerId, true)) { + + for (const { id } of this._editor.getDecorationsInRange(range) ?? []) { const metadata = this._decorationsMetadata.get(id); if (metadata) { decorationIdsToReplace.push(id); @@ -377,7 +379,7 @@ export class InlayHintsController implements IEditorContribution { } } } - const newDecorationIds = model.deltaDecorations(decorationIdsToReplace, newDecorationsData.map(d => d.decoration), this._decorationOwnerId); + const newDecorationIds = this._editor.deltaDecorations(decorationIdsToReplace, newDecorationsData.map(d => d.decoration)); for (let i = 0; i < newDecorationIds.length; i++) { const data = newDecorationsData[i]; this._decorationsMetadata.set(newDecorationIds[i], { hint: data.hint, classNameRef: data.classNameRef }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 443ee9b3450fc..6792053cde312 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5042,6 +5042,10 @@ declare namespace monaco.editor { * Get all the decorations on a line (filtering out decorations from other editors). */ getLineDecorations(lineNumber: number): IModelDecoration[] | null; + /** + * Get all the decorations for a range (filtering out decorations from other editors). + */ + getDecorationsInRange(range: Range): IModelDecoration[] | null; /** * All decorations added through this call will get the ownerId of this editor. * @see {@link ITextModel.deltaDecorations} From 270154ae22e53b4273701826128665992f9b6efd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 6 Jan 2022 14:23:35 +0100 Subject: [PATCH 1058/2210] add mechanics for inlay hovering, https://github.com/microsoft/vscode/issues/129528 --- .../contrib/hover/markdownHoverParticipant.ts | 4 +- .../editor/contrib/hover/modesContentHover.ts | 2 + .../inlayHints/inlayHintsController.ts | 20 ++++--- .../contrib/inlayHints/inlayHintsHover.ts | 52 +++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 src/vs/editor/contrib/inlayHints/inlayHintsHover.ts diff --git a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts index 2e3edaa73b51e..29a552299b2a9 100644 --- a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts @@ -45,8 +45,8 @@ export class MarkdownHover implements IHoverPart { export class MarkdownHoverParticipant implements IEditorHoverParticipant { constructor( - private readonly _editor: ICodeEditor, - private readonly _hover: IEditorHover, + protected readonly _editor: ICodeEditor, + protected readonly _hover: IEditorHover, @ILanguageService private readonly _languageService: ILanguageService, @IOpenerService private readonly _openerService: IOpenerService, @IConfigurationService private readonly _configurationService: IConfigurationService, diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index a6821039ed4d3..a4431655e4fda 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -33,6 +33,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest'; import { UnicodeHighlighterHoverParticipant } from 'vs/editor/contrib/unicodeHighlighter/unicodeHighlighter'; import { AsyncIterableObject } from 'vs/base/common/async'; +import { InlayHintsHover } from 'vs/editor/contrib/inlayHints/inlayHintsHover'; const $ = dom.$; @@ -228,6 +229,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I instantiationService.createInstance(InlineCompletionsHoverParticipant, editor, this), instantiationService.createInstance(UnicodeHighlighterHoverParticipant, editor, this), instantiationService.createInstance(MarkerHoverParticipant, editor, this), + instantiationService.createInstance(InlayHintsHover, editor, this), ]; this._editor = editor; diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index f8410ce022440..5898ee2ab8e06 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -103,8 +103,12 @@ class InlayHintsCache { } } -class InlayHintLink { - constructor(readonly href: string, readonly index: number, readonly hint: InlayHint) { } +export class InlayHintLink { + constructor(readonly href: string, readonly index: number) { } +} + +export class InlayHintData { + constructor(readonly hint: InlayHint, readonly link: InlayHintLink | undefined) { } } export class InlayHintsController implements IEditorContribution { @@ -121,7 +125,7 @@ export class InlayHintsController implements IEditorContribution { private readonly _cache = new InlayHintsCache(); private readonly _decorationsMetadata = new Map(); private readonly _ruleFactory = new DynamicCssRules(this._editor); - private _activeInlayHintLink?: InlayHintLink; + private _activeInlayHintLink?: InlayHintData; constructor( private readonly _editor: ICodeEditor, @@ -213,7 +217,7 @@ export class InlayHintsController implements IEditorContribution { } const model = this._editor.getModel()!; const options = mouseEvent.target.detail?.injectedText?.options; - if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLink) { + if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintData && options.attachedData.link) { this._activeInlayHintLink = options.attachedData; const lineNumber = this._activeInlayHintLink.hint.position.lineNumber; @@ -237,8 +241,8 @@ export class InlayHintsController implements IEditorContribution { return; } const options = e.target.detail?.injectedText?.options; - if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLink) { - this._openerService.open(options.attachedData.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); + if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintData && options.attachedData.link) { + this._openerService.open(options.attachedData.link.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); } })); } @@ -312,7 +316,7 @@ export class InlayHintsController implements IEditorContribution { if (isLink) { cssProperties.textDecoration = 'underline'; - if (this._activeInlayHintLink?.hint === hint && this._activeInlayHintLink.index === i && this._activeInlayHintLink.href === node.href) { + if (this._activeInlayHintLink?.hint === hint && this._activeInlayHintLink.link?.index === i && this._activeInlayHintLink.link.href === node.href) { // active link! cssProperties.cursor = 'pointer'; cssProperties.color = themeColorFromId(colors.editorActiveLinkForeground); @@ -350,7 +354,7 @@ export class InlayHintsController implements IEditorContribution { content: fixSpace(isLink ? node.label : node), inlineClassNameAffectsLetterSpacing: true, inlineClassName: classNameRef.className, - attachedData: isLink ? new InlayHintLink(node.href, i, hint) : undefined + attachedData: new InlayHintData(hint, isLink ? new InlayHintLink(node.href, i) : undefined) } as InjectedTextOptions, description: 'InlayHint', showIfCollapsed: !usesWordRange, diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts new file mode 100644 index 0000000000000..64d2c956d003a --- /dev/null +++ b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AsyncIterableObject } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import { InlayHint } from 'vs/editor/common/languages'; +import { IModelDecoration } from 'vs/editor/common/model'; +import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; +import { HoverAnchor, HoverForeignElementAnchor } from 'vs/editor/contrib/hover/hoverTypes'; +import { MarkdownHover, MarkdownHoverParticipant } from 'vs/editor/contrib/hover/markdownHoverParticipant'; +import { InlayHintData, InlayHintsController } from 'vs/editor/contrib/inlayHints/inlayHintsController'; + +class InlayHintsHoverAnchor extends HoverForeignElementAnchor { + + constructor(readonly hint: InlayHint, owner: InlayHintsHover) { + super(10, owner, Range.fromPositions(hint.position)); + } +} + +export class InlayHintsHover extends MarkdownHoverParticipant { + + suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { + const controller = InlayHintsController.get(this._editor); + if (!controller) { + return null; + } + if (mouseEvent.target.type !== MouseTargetType.CONTENT_TEXT || typeof mouseEvent.target.detail !== 'object') { + return null; + } + const options = mouseEvent.target.detail?.injectedText?.options; + if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintData)) { + return null; + } + return new InlayHintsHoverAnchor(options.attachedData.hint, this); + } + + override computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): MarkdownHover[] { + return []; + } + + override computeAsync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject { + if (!(anchor instanceof InlayHintsHoverAnchor)) { + return AsyncIterableObject.EMPTY; + } + return AsyncIterableObject.EMPTY; + // return AsyncIterableObject.fromArray([new MarkdownHover(this, anchor.range, [new MarkdownString(anchor.hint.text)], anchor.priority)]); + } +} From 0c933120f6fb8d095e66a02ac5287209cf14dcc8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 6 Jan 2022 14:35:44 +0100 Subject: [PATCH 1059/2210] add InlayHint#tooltip and wire it up with hover participant, https://github.com/microsoft/vscode/issues/129528 --- src/vs/editor/common/languages.ts | 1 + .../contrib/inlayHints/inlayHintsHover.ts | 17 +++++++++++++---- src/vs/monaco.d.ts | 1 + .../api/common/extHostTypeConverters.ts | 4 +++- src/vs/workbench/api/common/extHostTypes.ts | 1 + src/vscode-dts/vscode.proposed.inlayHints.d.ts | 4 ++++ 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 7a210421f6830..8e6f3e88b3804 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1723,6 +1723,7 @@ export enum InlayHintKind { export interface InlayHint { text: string; + tooltip?: string | IMarkdownString position: IPosition; kind: InlayHintKind; whitespaceBefore?: boolean; diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts index 64d2c956d003a..8b4fd308d23f3 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts @@ -5,6 +5,7 @@ import { AsyncIterableObject } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { InlayHint } from 'vs/editor/common/languages'; @@ -38,15 +39,23 @@ export class InlayHintsHover extends MarkdownHoverParticipant { return new InlayHintsHoverAnchor(options.attachedData.hint, this); } - override computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): MarkdownHover[] { + override computeSync(): MarkdownHover[] { return []; } - override computeAsync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject { + override computeAsync(anchor: HoverAnchor, _lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject { if (!(anchor instanceof InlayHintsHoverAnchor)) { return AsyncIterableObject.EMPTY; } - return AsyncIterableObject.EMPTY; - // return AsyncIterableObject.fromArray([new MarkdownHover(this, anchor.range, [new MarkdownString(anchor.hint.text)], anchor.priority)]); + if (!anchor.hint.tooltip) { + return AsyncIterableObject.EMPTY; + } + let md: IMarkdownString; + if (typeof anchor.hint.tooltip === 'string') { + md = new MarkdownString().appendText(anchor.hint.tooltip); + } else { + md = anchor.hint.tooltip; + } + return new AsyncIterableObject(emitter => emitter.emitOne(new MarkdownHover(this, anchor.range, [md], 0))); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6792053cde312..426b2dca3e455 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6758,6 +6758,7 @@ declare namespace monaco.languages { export interface InlayHint { text: string; + tooltip?: string | IMarkdownString; position: IPosition; kind: InlayHintKind; whitespaceBefore?: boolean; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 91f84fb152e91..2411c23b3cd7f 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1155,10 +1155,11 @@ export namespace InlayHint { export function from(hint: vscode.InlayHint): modes.InlayHint { return { text: hint.text, + tooltip: hint.tooltip && MarkdownString.from(hint.tooltip), position: Position.from(hint.position), kind: InlayHintKind.from(hint.kind ?? types.InlayHintKind.Other), whitespaceBefore: hint.whitespaceBefore, - whitespaceAfter: hint.whitespaceAfter + whitespaceAfter: hint.whitespaceAfter, }; } @@ -1168,6 +1169,7 @@ export namespace InlayHint { Position.to(hint.position), InlayHintKind.to(hint.kind) ); + res.tooltip = htmlContent.isMarkdownString(hint.tooltip) ? MarkdownString.to(hint.tooltip) : hint.tooltip; res.whitespaceAfter = hint.whitespaceAfter; res.whitespaceBefore = hint.whitespaceBefore; return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 6ea68075b13ee..789b7cc752754 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1423,6 +1423,7 @@ export enum InlayHintKind { @es5ClassCompat export class InlayHint { text: string; + tooltip?: string | vscode.MarkdownString; position: Position; kind?: vscode.InlayHintKind; whitespaceBefore?: boolean; diff --git a/src/vscode-dts/vscode.proposed.inlayHints.d.ts b/src/vscode-dts/vscode.proposed.inlayHints.d.ts index 9b3a30a7e3fcc..fe3d9ce0b66d3 100644 --- a/src/vscode-dts/vscode.proposed.inlayHints.d.ts +++ b/src/vscode-dts/vscode.proposed.inlayHints.d.ts @@ -43,6 +43,10 @@ declare module 'vscode' { */ // todo@API label? text: string; + /** + * The tooltip text when you hover over this item. + */ + tooltip?: string | MarkdownString | undefined; /** * The position of this hint. */ From d3942e1ac1840d8c14537909e6b88a6d226502b7 Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Thu, 6 Jan 2022 13:52:47 +0000 Subject: [PATCH 1060/2210] Surface the maximum and minimum values for `editor.hover.delay` (fixes #140215) --- src/vs/editor/common/config/editorOptions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index daa2393503738..62be0dff7b47b 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1845,6 +1845,8 @@ class EditorHover extends BaseEditorOption Date: Thu, 6 Jan 2022 15:45:37 +0100 Subject: [PATCH 1061/2210] Fixes injected text inline classname issue for wrapped lines. --- .../common/viewModel/modelLineProjection.ts | 9 ++++-- .../viewModel/modelLineProjection.test.ts | 29 ++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/viewModel/modelLineProjection.ts b/src/vs/editor/common/viewModel/modelLineProjection.ts index 04ab3ea68b9b3..b95f041fdca6d 100644 --- a/src/vs/editor/common/viewModel/modelLineProjection.ts +++ b/src/vs/editor/common/viewModel/modelLineProjection.ts @@ -198,8 +198,13 @@ class ModelLineProjection implements IModelLineProjection { } } - totalInjectedTextLengthBefore += length; - currentInjectedOffset++; + if (injectedTextEndOffsetInInputWithInjections <= lineEndOffsetInInputWithInjections) { + totalInjectedTextLengthBefore += length; + currentInjectedOffset++; + } else { + // injected text breaks into next line, process it again + break; + } } } } diff --git a/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts b/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts index 982656b58a344..15c6d75a0decb 100644 --- a/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts +++ b/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts @@ -745,7 +745,8 @@ suite('SplitLinesCollection', () => { options: { description: 'example', after: { - content: 'very very long injected text that causes a line break' + content: 'very very long injected text that causes a line break', + inlineClassName: 'myClassName' }, showIfCollapsed: true, } @@ -908,6 +909,32 @@ suite('SplitLinesCollection', () => { _expected[10], _expected[11], ]); + + const data = splitLinesCollection.getViewLinesData(1, 14, new Array(14).fill(true)); + assert.deepStrictEqual( + data.map((d) => ({ + inlineDecorations: d.inlineDecorations?.map((d) => ({ + startOffset: d.startOffset, + endOffset: d.endOffset, + })), + })), + [ + { inlineDecorations: [{ startOffset: 8, endOffset: 23 }] }, + { inlineDecorations: [{ startOffset: 4, endOffset: 42 }] }, + { inlineDecorations: [{ startOffset: 4, endOffset: 16 }] }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + { inlineDecorations: undefined }, + ] + ); }); }); From 59b42fd43d348fe5b4f3dbd4dbd0df3c0e3e4cfe Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 6 Jan 2022 16:08:14 +0100 Subject: [PATCH 1062/2210] Adds test for runAndSubscribeWithStore. --- src/vs/base/test/common/event.test.ts | 32 ++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 9fa0a7fb49c39..3dbfe4292c372 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -7,7 +7,7 @@ import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { AsyncEmitter, DebounceEmitter, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay } from 'vs/base/common/event'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; namespace Samples { @@ -956,4 +956,34 @@ suite('Event utils', () => { assert.deepStrictEqual(result, [2, 4]); }); }); + + test('runAndSubscribeWithStore', () => { + const eventEmitter = new Emitter(); + const event = eventEmitter.event; + + let i = 0; + let log = new Array(); + const disposable = Event.runAndSubscribeWithStore(event, (e, disposables) => { + const idx = i++; + log.push({ label: 'handleEvent', data: e || null, idx }); + disposables.add(toDisposable(() => { + log.push({ label: 'dispose', idx }); + })); + }); + + log.push({ label: 'fire' }); + eventEmitter.fire('someEventData'); + + log.push({ label: 'disposeAll' }); + disposable.dispose(); + + assert.deepStrictEqual(log, [ + { label: 'handleEvent', data: null, idx: 0 }, + { label: 'fire' }, + { label: 'dispose', idx: 0 }, + { label: 'handleEvent', data: 'someEventData', idx: 1 }, + { label: 'disposeAll' }, + { label: 'dispose', idx: 1 }, + ]); + }); }); From e5248460b669bbad5b651f9c1e98b1e7a625e94f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 6 Jan 2022 16:08:33 +0100 Subject: [PATCH 1063/2210] smart -> auto --- .../contrib/audioCues/browser/audioCueContribution.ts | 4 ++-- .../contrib/audioCues/browser/audioCues.contribution.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts index 5a8184b16639e..b05daf21f755a 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts @@ -59,10 +59,10 @@ export class AudioCueContribution extends DisposableStore implements IWorkbenchC } private get audioCuesEnabled(): boolean { - const value = this._configurationService.getValue<'smart' | 'on' | 'off'>('audioCues.enabled'); + const value = this._configurationService.getValue<'auto' | 'on' | 'off'>('audioCues.enabled'); if (value === 'on') { return true; - } else if (value === 'smart') { + } else if (value === 'auto') { return this.accessibilityService.isScreenReaderOptimized(); } else { return false; diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 7356c411cdd87..d3217f86cefc5 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -17,10 +17,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'audioCues.enabled': { 'type': 'string', 'description': localize('audioCues.enabled', "Controls whether audio cues are enabled."), - 'enum': ['smart', 'on', 'off'], - 'default': 'smart', + 'enum': ['auto', 'on', 'off'], + 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.smart', "Enable audio cues when a screen reader is attached."), + localize('audioCues.enabled.auto', "Enable audio cues when a screen reader is attached."), localize('audioCues.enabled.on', "Enable audio cues."), localize('audioCues.enabled.off', "Disable audio cues.") ], From 61d58205ce5c83bb3961b80fc68174681b46aefa Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 6 Jan 2022 16:15:08 +0100 Subject: [PATCH 1064/2210] Support diff editors. --- .../audioCues/browser/audioCueContribution.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts index b05daf21f755a..e615384951e3a 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts @@ -8,7 +8,7 @@ import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Event } from 'vs/base/common/event'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { raceTimeout } from 'vs/base/common/async'; import { FileAccess } from 'vs/base/common/network'; @@ -27,14 +27,16 @@ export class AudioCueContribution extends DisposableStore implements IWorkbenchC let lastLineNumber = -1; const activeTextEditorControl = editorService.activeTextEditorControl; - if (isCodeEditor(activeTextEditorControl)) { + if (isCodeEditor(activeTextEditorControl) || isDiffEditor(activeTextEditorControl)) { + const editor = isDiffEditor(activeTextEditorControl) ? activeTextEditorControl.getOriginalEditor() : activeTextEditorControl; + store.add( - activeTextEditorControl.onDidChangeCursorPosition(() => { - const model = activeTextEditorControl.getModel(); + editor.onDidChangeCursorPosition(() => { + const model = editor.getModel(); if (!model) { return; } - const position = activeTextEditorControl.getPosition(); + const position = editor.getPosition(); if (!position) { return; } From 4e923613802595b32a04b551e6030749975bdf65 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 6 Jan 2022 16:16:41 +0100 Subject: [PATCH 1065/2210] Configures i18n for vs/workbench/contrib/audioCues. --- build/lib/i18n.resources.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 2c01da4705892..e5056d4293383 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -262,6 +262,10 @@ "name": "vs/workbench/contrib/languageDetection", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/audioCues", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/actions", "project": "vscode-workbench" From 3677cc48a854386a9344452fe4965df09cf42c05 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 6 Jan 2022 16:28:30 +0100 Subject: [PATCH 1066/2210] support to resolve inlay hints for hover and for future display parts, https://github.com/microsoft/vscode/issues/129528 --- src/vs/editor/common/languages.ts | 10 +- .../editor/contrib/inlayHints/inlayHints.ts | 87 +++++++++++ .../inlayHints/inlayHintsController.ts | 143 +++++------------- .../contrib/inlayHints/inlayHintsHover.ts | 36 +++-- src/vs/monaco.d.ts | 10 +- .../api/browser/mainThreadLanguageFeatures.ts | 36 ++++- .../workbench/api/common/extHost.protocol.ts | 9 +- .../api/common/extHostLanguageFeatures.ts | 60 +++++++- .../api/common/extHostTypeConverters.ts | 4 +- .../vscode.proposed.inlayHints.d.ts | 6 +- 10 files changed, 264 insertions(+), 137 deletions(-) create mode 100644 src/vs/editor/contrib/inlayHints/inlayHints.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 8e6f3e88b3804..bf79ed3455b17 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1722,7 +1722,7 @@ export enum InlayHintKind { } export interface InlayHint { - text: string; + label: string; tooltip?: string | IMarkdownString position: IPosition; kind: InlayHintKind; @@ -1730,9 +1730,15 @@ export interface InlayHint { whitespaceAfter?: boolean; } +export interface InlayHintList { + hints: InlayHint[]; + dispose(): void; +} + export interface InlayHintsProvider { onDidChangeInlayHints?: Event; - provideInlayHints(model: model.ITextModel, range: Range, token: CancellationToken): ProviderResult; + provideInlayHints(model: model.ITextModel, range: Range, token: CancellationToken): ProviderResult; + resolveInlayHint?(hint: InlayHint, token: CancellationToken): ProviderResult; } export interface SemanticTokensLegend { diff --git a/src/vs/editor/contrib/inlayHints/inlayHints.ts b/src/vs/editor/contrib/inlayHints/inlayHints.ts new file mode 100644 index 0000000000000..252c084ec4cb8 --- /dev/null +++ b/src/vs/editor/contrib/inlayHints/inlayHints.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { InlayHint, InlayHintList, InlayHintsProvider, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; + +export class InlayHintItem { + + readonly resolve: (token: CancellationToken) => Promise; + + constructor(readonly hint: InlayHint, provider: InlayHintsProvider) { + if (!provider.resolveInlayHint) { + this.resolve = async () => { }; + } else { + let isResolved = false; + this.resolve = async token => { + if (isResolved) { + return; + } + try { + const newHint = await provider.resolveInlayHint!(this.hint, token); + this.hint.tooltip = newHint?.tooltip ?? this.hint.tooltip; + this.hint.label = newHint?.label ?? this.hint.label; + isResolved = true; + } catch (err) { + onUnexpectedExternalError(err); + } + }; + } + } +} + +export class InlayHintsFragments { + + static async create(model: ITextModel, ranges: Range[], token: CancellationToken): Promise { + + const data: [InlayHintList, InlayHintsProvider][] = []; + + const promises = InlayHintsProviderRegistry.ordered(model).reverse().map(provider => ranges.map(async range => { + try { + const result = await provider.provideInlayHints(model, range, token); + if (result?.hints.length) { + data.push([result, provider]); + } + } catch (err) { + onUnexpectedExternalError(err); + } + })); + + await Promise.all(promises.flat()); + + return new InlayHintsFragments(data); + } + + private readonly _disposables = new DisposableStore(); + private readonly _onDidChange = new Emitter(); + + readonly onDidReceiveProviderSignal: Event = this._onDidChange.event; + readonly items: readonly InlayHintItem[]; + + private constructor(data: [InlayHintList, InlayHintsProvider][]) { + const items: InlayHintItem[] = []; + for (const [list, provider] of data) { + this._disposables.add(list); + for (let hint of list.hints) { + items.push(new InlayHintItem(hint, provider)); + } + if (provider.onDidChangeInlayHints) { + provider.onDidChangeInlayHints(this._onDidChange.fire, this._onDidChange, this._disposables); + } + } + this.items = items.sort((a, b) => Position.compare(a.hint.position, b.hint.position)); + } + + dispose(): void { + this._onDidChange.dispose(); + this._disposables.dispose(); + } +} diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 5898ee2ab8e06..22998dd6864bd 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -6,10 +6,9 @@ import { distinct } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { parseLinkedText } from 'vs/base/common/linkedText'; -import { LRUCache, ResourceMap } from 'vs/base/common/map'; +import { LRUCache } from 'vs/base/common/map'; import { IRange } from 'vs/base/common/range'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -17,15 +16,15 @@ import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { CssProperties, DynamicCssRules } from 'vs/editor/browser/editorDom'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { InlayHint, InlayHintKind, InlayHintsProvider, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; +import { InlayHint, InlayHintKind, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; import { LanguageFeatureRequestDelays } from 'vs/editor/common/languages/languageFeatureRegistry'; import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, IWordAtPosition, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ClickLinkGesture } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; +import { InlayHintItem, InlayHintsFragments } from 'vs/editor/contrib/inlayHints/inlayHints'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import * as colors from 'vs/platform/theme/common/colorRegistry'; @@ -33,67 +32,17 @@ import { themeColorFromId } from 'vs/platform/theme/common/themeService'; const MAX_DECORATORS = 1500; -class RequestMap { - - private readonly _data = new ResourceMap>(); - - push(model: ITextModel, provider: T): void { - const value = this._data.get(model.uri); - if (value === undefined) { - this._data.set(model.uri, new Set([provider])); - } else { - value.add(provider); - } - } - - pop(model: ITextModel, provider: T): void { - const value = this._data.get(model.uri); - if (value) { - value.delete(provider); - if (value.size === 0) { - this._data.delete(model.uri); - } - } - } - - has(model: ITextModel, provider: T): boolean { - return Boolean(this._data.get(model.uri)?.has(provider)); - } -} - -export async function getInlayHints(model: ITextModel, ranges: Range[], requests: RequestMap, token: CancellationToken): Promise { - const all: InlayHint[][] = []; - const providers = InlayHintsProviderRegistry.ordered(model).reverse(); - - const promises = providers.map(provider => ranges.map(async range => { - try { - requests.push(model, provider); - const result = await provider.provideInlayHints(model, range, token); - if (result?.length) { - all.push(result.filter(hint => range.containsPosition(hint.position))); - } - } catch (err) { - onUnexpectedExternalError(err); - } finally { - requests.pop(model, provider); - } - })); - - await Promise.all(promises.flat()); - - return all.flat().sort((a, b) => Position.compare(a.position, b.position)); -} class InlayHintsCache { - private readonly _entries = new LRUCache(50); + private readonly _entries = new LRUCache(50); - get(model: ITextModel): InlayHint[] | undefined { + get(model: ITextModel): InlayHintItem[] | undefined { const key = InlayHintsCache._key(model); return this._entries.get(key); } - set(model: ITextModel, value: InlayHint[]): void { + set(model: ITextModel, value: InlayHintItem[]): void { const key = InlayHintsCache._key(model); this._entries.set(key, value); } @@ -103,12 +52,8 @@ class InlayHintsCache { } } -export class InlayHintLink { - constructor(readonly href: string, readonly index: number) { } -} - -export class InlayHintData { - constructor(readonly hint: InlayHint, readonly link: InlayHintLink | undefined) { } +export class InlayHintLabelPart { + constructor(readonly item: InlayHintItem, readonly index: number, readonly href?: string) { } } export class InlayHintsController implements IEditorContribution { @@ -123,9 +68,10 @@ export class InlayHintsController implements IEditorContribution { private readonly _sessionDisposables = new DisposableStore(); private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 25, 500); private readonly _cache = new InlayHintsCache(); - private readonly _decorationsMetadata = new Map(); + private readonly _decorationsMetadata = new Map(); private readonly _ruleFactory = new DynamicCssRules(this._editor); - private _activeInlayHintLink?: InlayHintData; + + private _activeInlayHintPart?: InlayHintLabelPart; constructor( private readonly _editor: ICodeEditor, @@ -167,7 +113,6 @@ export class InlayHintsController implements IEditorContribution { this._updateHintsDecorators([model.getFullModelRange()], cached); } - const requests = new RequestMap(); const scheduler = new RunOnceScheduler(async () => { const t1 = Date.now(); @@ -176,13 +121,16 @@ export class InlayHintsController implements IEditorContribution { this._sessionDisposables.add(toDisposable(() => cts.dispose(true))); const ranges = this._getHintsRanges(); - const result = await getInlayHints(model, ranges, requests, cts.token); + const inlayHints = await InlayHintsFragments.create(model, ranges, cts.token); + this._sessionDisposables.add(inlayHints); + this._sessionDisposables.add(inlayHints.onDidReceiveProviderSignal(() => scheduler.schedule())); + scheduler.delay = this._getInlayHintsDelays.update(model, Date.now() - t1); if (cts.token.isCancellationRequested) { return; } - this._updateHintsDecorators(ranges, result); - this._cache.set(model, distinct(Array.from(this._decorationsMetadata.values(), obj => obj.hint))); + this._updateHintsDecorators(ranges, inlayHints.items); + this._cache.set(model, distinct(Array.from(this._decorationsMetadata.values(), obj => obj.item))); }, this._getInlayHintsDelays.get(model)); @@ -193,18 +141,6 @@ export class InlayHintsController implements IEditorContribution { this._sessionDisposables.add(this._editor.onDidScrollChange(() => scheduler.schedule())); scheduler.schedule(); - // update inline hints when any any provider fires an event - const providerListener = new DisposableStore(); - this._sessionDisposables.add(providerListener); - for (const provider of InlayHintsProviderRegistry.all(model)) { - if (typeof provider.onDidChangeInlayHints === 'function') { - providerListener.add(provider.onDidChangeInlayHints(() => { - if (!requests.has(model, provider)) { - scheduler.schedule(); - } - })); - } - } // link gesture let undoHover = () => { }; @@ -217,20 +153,20 @@ export class InlayHintsController implements IEditorContribution { } const model = this._editor.getModel()!; const options = mouseEvent.target.detail?.injectedText?.options; - if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintData && options.attachedData.link) { - this._activeInlayHintLink = options.attachedData; + if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { + this._activeInlayHintPart = options.attachedData; - const lineNumber = this._activeInlayHintLink.hint.position.lineNumber; + const lineNumber = this._activeInlayHintPart.item.hint.position.lineNumber; const range = new Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber)); - const lineHints = new Set(); + const lineHints = new Set(); for (let data of this._decorationsMetadata.values()) { - if (range.containsPosition(data.hint.position)) { - lineHints.add(data.hint); + if (range.containsPosition(data.item.hint.position)) { + lineHints.add(data.item); } } this._updateHintsDecorators([range], Array.from(lineHints)); undoHover = () => { - this._activeInlayHintLink = undefined; + this._activeInlayHintPart = undefined; this._updateHintsDecorators([range], Array.from(lineHints)); }; } @@ -241,8 +177,8 @@ export class InlayHintsController implements IEditorContribution { return; } const options = e.target.detail?.injectedText?.options; - if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintData && options.attachedData.link) { - this._openerService.open(options.attachedData.link.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); + if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { + this._openerService.open(options.attachedData.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); } })); } @@ -263,19 +199,19 @@ export class InlayHintsController implements IEditorContribution { return result; } - private _updateHintsDecorators(ranges: Range[], hints: InlayHint[]): void { + private _updateHintsDecorators(ranges: Range[], items: readonly InlayHintItem[]): void { const { fontSize, fontFamily } = this._getLayoutInfo(); const model = this._editor.getModel()!; - const newDecorationsData: { hint: InlayHint, decoration: IModelDeltaDecoration, classNameRef: IDisposable }[] = []; + const newDecorationsData: { item: InlayHintItem, decoration: IModelDeltaDecoration, classNameRef: IDisposable }[] = []; const fontFamilyVar = '--code-editorInlayHintsFontFamily'; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); - for (const hint of hints) { + for (const item of items) { - const { position, whitespaceBefore, whitespaceAfter } = hint; + const { position, whitespaceBefore, whitespaceAfter } = item.hint; // position let direction: 'before' | 'after' = 'before'; @@ -294,7 +230,7 @@ export class InlayHintsController implements IEditorContribution { } // text w/ links - const { nodes } = parseLinkedText(hint.text); + const { nodes } = parseLinkedText(item.hint.label); const marginBefore = whitespaceBefore ? (fontSize / 3) | 0 : 0; const marginAfter = whitespaceAfter ? (fontSize / 3) | 0 : 0; @@ -311,12 +247,12 @@ export class InlayHintsController implements IEditorContribution { verticalAlign: 'middle', }; - this._fillInColors(cssProperties, hint); + this._fillInColors(cssProperties, item.hint); if (isLink) { cssProperties.textDecoration = 'underline'; - if (this._activeInlayHintLink?.hint === hint && this._activeInlayHintLink.link?.index === i && this._activeInlayHintLink.link.href === node.href) { + if (this._activeInlayHintPart?.item === item && this._activeInlayHintPart.index === i && this._activeInlayHintPart.href === node.href) { // active link! cssProperties.cursor = 'pointer'; cssProperties.color = themeColorFromId(colors.editorActiveLinkForeground); @@ -345,7 +281,7 @@ export class InlayHintsController implements IEditorContribution { const classNameRef = this._ruleFactory.createClassNameRef(cssProperties); newDecorationsData.push({ - hint, + item: item, classNameRef, decoration: { range, @@ -354,7 +290,7 @@ export class InlayHintsController implements IEditorContribution { content: fixSpace(isLink ? node.label : node), inlineClassNameAffectsLetterSpacing: true, inlineClassName: classNameRef.className, - attachedData: new InlayHintData(hint, isLink ? new InlayHintLink(node.href, i) : undefined) + attachedData: new InlayHintLabelPart(item, i, isLink ? node.href : undefined) } as InjectedTextOptions, description: 'InlayHint', showIfCollapsed: !usesWordRange, @@ -386,7 +322,7 @@ export class InlayHintsController implements IEditorContribution { const newDecorationIds = this._editor.deltaDecorations(decorationIdsToReplace, newDecorationsData.map(d => d.decoration)); for (let i = 0; i < newDecorationIds.length; i++) { const data = newDecorationsData[i]; - this._decorationsMetadata.set(newDecorationIds[i], { hint: data.hint, classNameRef: data.classNameRef }); + this._decorationsMetadata.set(newDecorationIds[i], { item: data.item, classNameRef: data.classNameRef }); } } @@ -448,9 +384,10 @@ CommandsRegistry.registerCommand('_executeInlayHintProvider', async (accessor, . const ref = await accessor.get(ITextModelService).createModelReference(uri); try { - const data = await getInlayHints(ref.object.textEditorModel, [Range.lift(range)], new RequestMap(), CancellationToken.None); - return data; - + const model = await InlayHintsFragments.create(ref.object.textEditorModel, [Range.lift(range)], CancellationToken.None); + const result = model.items.map(i => i.hint); + setTimeout(() => model.dispose(), 0); // dispose after sending to ext host + return result; } finally { ref.dispose(); } diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts index 8b4fd308d23f3..cc3ff5c39452f 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts @@ -8,17 +8,17 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; -import { InlayHint } from 'vs/editor/common/languages'; import { IModelDecoration } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { HoverAnchor, HoverForeignElementAnchor } from 'vs/editor/contrib/hover/hoverTypes'; import { MarkdownHover, MarkdownHoverParticipant } from 'vs/editor/contrib/hover/markdownHoverParticipant'; -import { InlayHintData, InlayHintsController } from 'vs/editor/contrib/inlayHints/inlayHintsController'; +import { InlayHintItem } from 'vs/editor/contrib/inlayHints/inlayHints'; +import { InlayHintLabelPart, InlayHintsController } from 'vs/editor/contrib/inlayHints/inlayHintsController'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { - constructor(readonly hint: InlayHint, owner: InlayHintsHover) { - super(10, owner, Range.fromPositions(hint.position)); + constructor(readonly item: InlayHintItem, owner: InlayHintsHover) { + super(10, owner, Range.fromPositions(item.hint.position)); } } @@ -33,10 +33,10 @@ export class InlayHintsHover extends MarkdownHoverParticipant { return null; } const options = mouseEvent.target.detail?.injectedText?.options; - if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintData)) { + if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart)) { return null; } - return new InlayHintsHoverAnchor(options.attachedData.hint, this); + return new InlayHintsHoverAnchor(options.attachedData.item, this); } override computeSync(): MarkdownHover[] { @@ -47,15 +47,19 @@ export class InlayHintsHover extends MarkdownHoverParticipant { if (!(anchor instanceof InlayHintsHoverAnchor)) { return AsyncIterableObject.EMPTY; } - if (!anchor.hint.tooltip) { - return AsyncIterableObject.EMPTY; - } - let md: IMarkdownString; - if (typeof anchor.hint.tooltip === 'string') { - md = new MarkdownString().appendText(anchor.hint.tooltip); - } else { - md = anchor.hint.tooltip; - } - return new AsyncIterableObject(emitter => emitter.emitOne(new MarkdownHover(this, anchor.range, [md], 0))); + + const { item } = anchor; + return AsyncIterableObject.fromPromise(item.resolve(token).then(() => { + if (!item.hint.tooltip) { + return []; + } + let contents: IMarkdownString; + if (typeof item.hint.tooltip === 'string') { + contents = new MarkdownString().appendText(item.hint.tooltip); + } else { + contents = item.hint.tooltip; + } + return [new MarkdownHover(this, anchor.range, [contents], 0)]; + })); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 426b2dca3e455..9e03ba62ae9e3 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6757,7 +6757,7 @@ declare namespace monaco.languages { } export interface InlayHint { - text: string; + label: string; tooltip?: string | IMarkdownString; position: IPosition; kind: InlayHintKind; @@ -6765,9 +6765,15 @@ declare namespace monaco.languages { whitespaceAfter?: boolean; } + export interface InlayHintList { + hints: InlayHint[]; + dispose(): void; + } + export interface InlayHintsProvider { onDidChangeInlayHints?: IEvent; - provideInlayHints(model: editor.ITextModel, range: Range, token: CancellationToken): ProviderResult; + provideInlayHints(model: editor.ITextModel, range: Range, token: CancellationToken): ProviderResult; + resolveInlayHint?(hint: InlayHint, token: CancellationToken): ProviderResult; } export interface SemanticTokensLegend { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index c0c67de4a6089..60cdd4766751f 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto, IInlayHintDto } from '../common/extHost.protocol'; import { ILanguageConfigurationService, LanguageConfigurationRegistry } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/languages/languageConfiguration'; import { ILanguageService } from 'vs/editor/common/services/language'; @@ -549,14 +549,40 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- inline hints - $registerInlayHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void { + $registerInlayHintsProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean, eventHandle: number | undefined): void { const provider = { - provideInlayHints: async (model: ITextModel, range: EditorRange, token: CancellationToken): Promise => { + provideInlayHints: async (model: ITextModel, range: EditorRange, token: CancellationToken): Promise => { const result = await this._proxy.$provideInlayHints(handle, model.uri, range, token); - return result?.hints; + if (!result) { + return; + } + return { + hints: result.hints, + dispose: () => { + if (result.cacheId) { + this._proxy.$releaseInlayHints(handle, result.cacheId); + } + } + }; } }; - + if (supportsResolve) { + provider.resolveInlayHint = async (hint, token) => { + const dto: IInlayHintDto = hint; + if (!dto.cacheId) { + return hint; + } + const result = await this._proxy.$resolveInlayHint(handle, dto.cacheId, token); + if (!result) { + return hint; + } + return { + ...hint, + tooltip: result.tooltip, + label: result.label + }; + }; + } if (typeof eventHandle === 'number') { const emitter = new Emitter(); this._registrations.set(eventHandle, emitter); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 87be1511e36cb..d5a048e8ae524 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -421,7 +421,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, displayName: string): void; $registerInlineCompletionsSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; - $registerInlayHintsProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void; + $registerInlayHintsProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean, eventHandle: number | undefined): void; $emitInlayHintsEvent(eventHandle: number): void; $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; $registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void; @@ -1503,7 +1503,9 @@ export interface ISignatureHelpContextDto { } export interface IInlayHintDto { - text: string; + cacheId?: ChainedCacheId; + label: string; + tooltip?: string | IMarkdownString; position: IPosition; kind: modes.InlayHintKind; whitespaceBefore?: boolean; @@ -1511,6 +1513,7 @@ export interface IInlayHintDto { } export interface IInlayHintsDto { + cacheId?: CacheId hints: IInlayHintDto[] } @@ -1722,6 +1725,8 @@ export interface ExtHostLanguageFeaturesShape { $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; $releaseSignatureHelp(handle: number, id: number): void; $provideInlayHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise + $resolveInlayHint(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; + $releaseInlayHints(handle: number, id: number): void; $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseDocumentLinks(handle: number, id: number): void; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index a969d137a3fc1..5bcf93a9dbc66 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1171,6 +1171,9 @@ class SignatureHelpAdapter { } class InlayHintsAdapter { + + private _cache = new Cache('InlayHints'); + constructor( private readonly _documents: ExtHostDocuments, private readonly _provider: vscode.InlayHintsProvider, @@ -1178,8 +1181,51 @@ class InlayHintsAdapter { async provideInlayHints(resource: URI, range: IRange, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); - const value = await this._provider.provideInlayHints(doc, typeConvert.Range.to(range), token); - return value ? { hints: value.map(typeConvert.InlayHint.from) } : undefined; + + const hints = await this._provider.provideInlayHints(doc, typeConvert.Range.to(range), token); + if (!Array.isArray(hints) || hints.length === 0) { + // bad result + return undefined; + } + if (token.isCancellationRequested) { + // cancelled -> return without further ado, esp no caching + // of results as they will leak + return undefined; + } + if (typeof this._provider.resolveInlayHint !== 'function') { + // no resolve -> no caching + return { hints: hints.map(typeConvert.InlayHint.from) }; + + } else { + // cache links for future resolving + const pid = this._cache.add(hints); + const result: extHostProtocol.IInlayHintsDto = { hints: [], cacheId: pid }; + for (let i = 0; i < hints.length; i++) { + const dto: extHostProtocol.IInlayHintDto = typeConvert.InlayHint.from(hints[i]); + dto.cacheId = [pid, i]; + result.hints.push(dto); + } + return result; + } + } + + async resolveInlayHint(id: extHostProtocol.ChainedCacheId, token: CancellationToken) { + if (typeof this._provider.resolveInlayHint !== 'function') { + return undefined; + } + const item = this._cache.get(...id); + if (!item) { + return undefined; + } + const hint = await this._provider.resolveInlayHint!(item, token); + if (!hint) { + return undefined; + } + return typeConvert.InlayHint.from(hint); + } + + releaseHints(id: number): any { + this._cache.delete(id); } } @@ -1986,7 +2032,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF const eventHandle = typeof provider.onDidChangeInlayHints === 'function' ? this._nextHandle() : undefined; const handle = this._addNewAdapter(new InlayHintsAdapter(this._documents, provider), extension); - this._proxy.$registerInlayHintsProvider(handle, this._transformDocumentSelector(selector), eventHandle); + this._proxy.$registerInlayHintsProvider(handle, this._transformDocumentSelector(selector), typeof provider.resolveInlayHint === 'function', eventHandle); let result = this._createDisposable(handle); if (eventHandle !== undefined) { @@ -2000,6 +2046,14 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, InlayHintsAdapter, adapter => adapter.provideInlayHints(URI.revive(resource), range, token), undefined); } + $resolveInlayHint(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise { + return this._withAdapter(handle, InlayHintsAdapter, adapter => adapter.resolveInlayHint(id, token), undefined); + } + + $releaseInlayHints(handle: number, id: number): void { + this._withAdapter(handle, InlayHintsAdapter, adapter => adapter.releaseHints(id), undefined); + } + // --- links registerDocumentLinkProvider(extension: IExtensionDescription | undefined, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2411c23b3cd7f..072dfa84e35b3 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1154,7 +1154,7 @@ export namespace InlayHint { export function from(hint: vscode.InlayHint): modes.InlayHint { return { - text: hint.text, + label: hint.text, tooltip: hint.tooltip && MarkdownString.from(hint.tooltip), position: Position.from(hint.position), kind: InlayHintKind.from(hint.kind ?? types.InlayHintKind.Other), @@ -1165,7 +1165,7 @@ export namespace InlayHint { export function to(hint: modes.InlayHint): vscode.InlayHint { const res = new types.InlayHint( - hint.text, + hint.label, Position.to(hint.position), InlayHintKind.to(hint.kind) ); diff --git a/src/vscode-dts/vscode.proposed.inlayHints.d.ts b/src/vscode-dts/vscode.proposed.inlayHints.d.ts index fe3d9ce0b66d3..95f7c964f621b 100644 --- a/src/vscode-dts/vscode.proposed.inlayHints.d.ts +++ b/src/vscode-dts/vscode.proposed.inlayHints.d.ts @@ -72,7 +72,7 @@ declare module 'vscode' { * The inlay hints provider interface defines the contract between extensions and * the inlay hints feature. */ - export interface InlayHintsProvider { + export interface InlayHintsProvider { /** * An optional event to signal that inlay hints have changed. @@ -88,6 +88,8 @@ declare module 'vscode' { * @param token A cancellation token. * @return A list of inlay hints or a thenable that resolves to such. */ - provideInlayHints(model: TextDocument, range: Range, token: CancellationToken): ProviderResult; + provideInlayHints(model: TextDocument, range: Range, token: CancellationToken): ProviderResult; + + resolveInlayHint?(hint: T, token: CancellationToken): ProviderResult } } From ca8663578194091e926e3f58f5bf6a44392046e6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 6 Jan 2022 16:42:36 +0100 Subject: [PATCH 1067/2210] Fixes lint error. --- .../workbench/contrib/audioCues/browser/audioCueContribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts index e615384951e3a..0e4d35cf9d2c7 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts @@ -8,7 +8,7 @@ import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Event } from 'vs/base/common/event'; -import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { raceTimeout } from 'vs/base/common/async'; import { FileAccess } from 'vs/base/common/network'; From a0b0180a5a910444e168574659666a211b92121e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 6 Jan 2022 08:14:23 -0800 Subject: [PATCH 1068/2210] Remember vlaid absolute executable when resolving profile Fixes #140015 --- src/vs/platform/terminal/common/terminal.ts | 5 +++++ src/vs/platform/terminal/node/terminalProfiles.ts | 2 ++ .../contrib/terminal/browser/terminalProfileQuickpick.ts | 6 ++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 87f55e99f74f3..6ef19a0e5626c 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -689,6 +689,11 @@ export interface ITerminalProfile { path: string; isDefault: boolean; isAutoDetected?: boolean; + /** + * Whether the profile path was found on the `$PATH` environment variable, if so it will be + * cleaner to display this profile in the UI using only `basename(path)`. + */ + isFromPath?: boolean; args?: string | string[] | undefined; env?: ITerminalEnvironment; overrideName?: boolean; diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index 196f8545008f1..dce492a549bc3 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -353,6 +353,8 @@ async function validateProfilePaths(profileName: string, defaultProfileName: str if (!executable) { return validateProfilePaths(profileName, defaultProfileName, potentialPaths, fsProvider, shellEnv, args); } + profile.path = executable; + profile.isFromPath = true; return profile; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index 04abfd38d35f4..58b112505bd28 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -15,6 +15,7 @@ import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/te import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; +import { basename } from 'vs/base/common/path'; type DefaultProfileName = string; @@ -208,6 +209,7 @@ export class TerminalProfileQuickpick { }]; const icon = (profile.icon && ThemeIcon.isThemeIcon(profile.icon)) ? profile.icon : Codicon.terminal; const label = `$(${icon.id}) ${profile.profileName}`; + const friendlyPath = profile.isFromPath ? basename(profile.path) : profile.path; const colorClass = getColorClass(profile); const iconClasses = []; if (colorClass) { @@ -224,9 +226,9 @@ export class TerminalProfileQuickpick { } return e; }).join(' '); - return { label, description: `${profile.path} ${argsString}`, profile, profileName: profile.profileName, buttons, iconClasses }; + return { label, description: `${friendlyPath} ${argsString}`, profile, profileName: profile.profileName, buttons, iconClasses }; } - return { label, description: profile.path, profile, profileName: profile.profileName, buttons, iconClasses }; + return { label, description: friendlyPath, profile, profileName: profile.profileName, buttons, iconClasses }; } private _sortProfileQuickPickItems(items: IProfileQuickPickItem[], defaultProfileName: string) { From d9e8526f7bf4255f42a3c82fda18a3fed15609c1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 6 Jan 2022 08:18:16 -0800 Subject: [PATCH 1069/2210] Revert "Remember vlaid absolute executable when resolving profile" This reverts commit a0b0180a5a910444e168574659666a211b92121e. --- src/vs/platform/terminal/common/terminal.ts | 5 ----- src/vs/platform/terminal/node/terminalProfiles.ts | 2 -- .../contrib/terminal/browser/terminalProfileQuickpick.ts | 6 ++---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 6ef19a0e5626c..87f55e99f74f3 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -689,11 +689,6 @@ export interface ITerminalProfile { path: string; isDefault: boolean; isAutoDetected?: boolean; - /** - * Whether the profile path was found on the `$PATH` environment variable, if so it will be - * cleaner to display this profile in the UI using only `basename(path)`. - */ - isFromPath?: boolean; args?: string | string[] | undefined; env?: ITerminalEnvironment; overrideName?: boolean; diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index dce492a549bc3..196f8545008f1 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -353,8 +353,6 @@ async function validateProfilePaths(profileName: string, defaultProfileName: str if (!executable) { return validateProfilePaths(profileName, defaultProfileName, potentialPaths, fsProvider, shellEnv, args); } - profile.path = executable; - profile.isFromPath = true; return profile; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index 58b112505bd28..04abfd38d35f4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -15,7 +15,6 @@ import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/te import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; -import { basename } from 'vs/base/common/path'; type DefaultProfileName = string; @@ -209,7 +208,6 @@ export class TerminalProfileQuickpick { }]; const icon = (profile.icon && ThemeIcon.isThemeIcon(profile.icon)) ? profile.icon : Codicon.terminal; const label = `$(${icon.id}) ${profile.profileName}`; - const friendlyPath = profile.isFromPath ? basename(profile.path) : profile.path; const colorClass = getColorClass(profile); const iconClasses = []; if (colorClass) { @@ -226,9 +224,9 @@ export class TerminalProfileQuickpick { } return e; }).join(' '); - return { label, description: `${friendlyPath} ${argsString}`, profile, profileName: profile.profileName, buttons, iconClasses }; + return { label, description: `${profile.path} ${argsString}`, profile, profileName: profile.profileName, buttons, iconClasses }; } - return { label, description: friendlyPath, profile, profileName: profile.profileName, buttons, iconClasses }; + return { label, description: profile.path, profile, profileName: profile.profileName, buttons, iconClasses }; } private _sortProfileQuickPickItems(items: IProfileQuickPickItem[], defaultProfileName: string) { From 6ef041e33856f453b5afb49746a7304124776704 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 6 Jan 2022 08:25:05 -0800 Subject: [PATCH 1070/2210] Remember valid absolute executable when resolving profile With tests this time Fixes #140015 --- src/vs/platform/terminal/common/terminal.ts | 5 +++++ src/vs/platform/terminal/node/terminalProfiles.ts | 2 ++ .../contrib/terminal/browser/terminalProfileQuickpick.ts | 6 ++++-- .../contrib/terminal/test/node/terminalProfiles.test.ts | 6 +++--- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 87f55e99f74f3..6ef19a0e5626c 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -689,6 +689,11 @@ export interface ITerminalProfile { path: string; isDefault: boolean; isAutoDetected?: boolean; + /** + * Whether the profile path was found on the `$PATH` environment variable, if so it will be + * cleaner to display this profile in the UI using only `basename(path)`. + */ + isFromPath?: boolean; args?: string | string[] | undefined; env?: ITerminalEnvironment; overrideName?: boolean; diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index 196f8545008f1..dce492a549bc3 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -353,6 +353,8 @@ async function validateProfilePaths(profileName: string, defaultProfileName: str if (!executable) { return validateProfilePaths(profileName, defaultProfileName, potentialPaths, fsProvider, shellEnv, args); } + profile.path = executable; + profile.isFromPath = true; return profile; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index 04abfd38d35f4..58b112505bd28 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -15,6 +15,7 @@ import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/te import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; +import { basename } from 'vs/base/common/path'; type DefaultProfileName = string; @@ -208,6 +209,7 @@ export class TerminalProfileQuickpick { }]; const icon = (profile.icon && ThemeIcon.isThemeIcon(profile.icon)) ? profile.icon : Codicon.terminal; const label = `$(${icon.id}) ${profile.profileName}`; + const friendlyPath = profile.isFromPath ? basename(profile.path) : profile.path; const colorClass = getColorClass(profile); const iconClasses = []; if (colorClass) { @@ -224,9 +226,9 @@ export class TerminalProfileQuickpick { } return e; }).join(' '); - return { label, description: `${profile.path} ${argsString}`, profile, profileName: profile.profileName, buttons, iconClasses }; + return { label, description: `${friendlyPath} ${argsString}`, profile, profileName: profile.profileName, buttons, iconClasses }; } - return { label, description: profile.path, profile, profileName: profile.profileName, buttons, iconClasses }; + return { label, description: friendlyPath, profile, profileName: profile.profileName, buttons, iconClasses }; } private _sortProfileQuickPickItems(items: IProfileQuickPickItem[], defaultProfileName: string) { diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts index 2b2f7100ae106..3fc4cf03f2279 100644 --- a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts @@ -200,8 +200,8 @@ suite('Workbench - TerminalProfiles', () => { const configurationService = new TestConfigurationService({ terminal: { integrated: onPathConfig } }); const profiles = await detectAvailableProfiles(undefined, undefined, true, configurationService, process.env, fsProvider, undefined, undefined, undefined); const expected: ITerminalProfile[] = [ - { profileName: 'fakeshell1', path: 'fakeshell1', isDefault: true }, - { profileName: 'fakeshell3', path: 'fakeshell3', isDefault: true } + { profileName: 'fakeshell1', path: '/bin/fakeshell1', isFromPath: true, isDefault: true }, + { profileName: 'fakeshell3', path: '/bin/fakeshell3', isFromPath: true, isDefault: true } ]; profilesEqual(profiles, expected); }); @@ -213,7 +213,7 @@ suite('Workbench - TerminalProfiles', () => { const configurationService = new TestConfigurationService({ terminal: { integrated: onPathConfig } }); const profiles = await detectAvailableProfiles(undefined, undefined, true, configurationService, process.env, fsProvider, undefined, undefined, undefined); const expected: ITerminalProfile[] = [ - { profileName: 'fakeshell1', path: 'fakeshell1', isDefault: true } + { profileName: 'fakeshell1', path: '/bin/fakeshell1', isFromPath: true, isDefault: true } ]; profilesEqual(profiles, expected); }); From e32cdfd97c65a5b07af98d05009fd0743e30d4e9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 6 Jan 2022 17:27:47 +0100 Subject: [PATCH 1071/2210] some cleanup, also make sure to cache inlays with most recent range (as updated by the editor), https://github.com/microsoft/vscode/issues/16221 --- .../editor/contrib/inlayHints/inlayHints.ts | 41 ++++++++- .../inlayHints/inlayHintsController.ts | 91 +++++++++---------- 2 files changed, 79 insertions(+), 53 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/inlayHints.ts b/src/vs/editor/contrib/inlayHints/inlayHints.ts index 252c084ec4cb8..241950b5f5447 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHints.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHints.ts @@ -10,13 +10,17 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { InlayHint, InlayHintList, InlayHintsProvider, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; -import { ITextModel } from 'vs/editor/common/model'; +import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; + +export class InlayHintAnchor { + constructor(public range: Range, readonly direction: 'before' | 'after', readonly usesWordRange: boolean) { } +} export class InlayHintItem { readonly resolve: (token: CancellationToken) => Promise; - constructor(readonly hint: InlayHint, provider: InlayHintsProvider) { + constructor(readonly hint: InlayHint, readonly anchor: InlayHintAnchor, provider: InlayHintsProvider) { if (!provider.resolveInlayHint) { this.resolve = async () => { }; } else { @@ -57,7 +61,7 @@ export class InlayHintsFragments { await Promise.all(promises.flat()); - return new InlayHintsFragments(data); + return new InlayHintsFragments(data, model); } private readonly _disposables = new DisposableStore(); @@ -66,12 +70,30 @@ export class InlayHintsFragments { readonly onDidReceiveProviderSignal: Event = this._onDidChange.event; readonly items: readonly InlayHintItem[]; - private constructor(data: [InlayHintList, InlayHintsProvider][]) { + private constructor(data: [InlayHintList, InlayHintsProvider][], model: ITextModel) { const items: InlayHintItem[] = []; for (const [list, provider] of data) { this._disposables.add(list); for (let hint of list.hints) { - items.push(new InlayHintItem(hint, provider)); + + // compute the range to which the item should be attached to + let position = hint.position; + let direction: 'before' | 'after' = 'before'; + let range = Range.fromPositions(position); + let word = model.getWordAtPosition(position); + let usesWordRange = false; + if (word) { + if (word.endColumn === position.column) { + direction = 'after'; + usesWordRange = true; + range = wordToRange(word, position.lineNumber); + } else if (word.startColumn === position.column) { + usesWordRange = true; + range = wordToRange(word, position.lineNumber); + } + } + + items.push(new InlayHintItem(hint, new InlayHintAnchor(range, direction, usesWordRange), provider)); } if (provider.onDidChangeInlayHints) { provider.onDidChangeInlayHints(this._onDidChange.fire, this._onDidChange, this._disposables); @@ -85,3 +107,12 @@ export class InlayHintsFragments { this._disposables.dispose(); } } + +function wordToRange(word: IWordAtPosition, lineNumber: number): Range { + return new Range( + lineNumber, + word.startColumn, + lineNumber, + word.endColumn + ); +} diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 22998dd6864bd..89acb885df851 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { distinct } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -20,7 +19,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { InlayHint, InlayHintKind, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; import { LanguageFeatureRequestDelays } from 'vs/editor/common/languages/languageFeatureRegistry'; -import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, IWordAtPosition, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ClickLinkGesture } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; @@ -113,7 +112,6 @@ export class InlayHintsController implements IEditorContribution { this._updateHintsDecorators([model.getFullModelRange()], cached); } - const scheduler = new RunOnceScheduler(async () => { const t1 = Date.now(); @@ -130,7 +128,7 @@ export class InlayHintsController implements IEditorContribution { return; } this._updateHintsDecorators(ranges, inlayHints.items); - this._cache.set(model, distinct(Array.from(this._decorationsMetadata.values(), obj => obj.item))); + this._cacheHintsForFastRestore(model); }, this._getInlayHintsDelays.get(model)); @@ -141,14 +139,19 @@ export class InlayHintsController implements IEditorContribution { this._sessionDisposables.add(this._editor.onDidScrollChange(() => scheduler.schedule())); scheduler.schedule(); - // link gesture - let undoHover = () => { }; - const gesture = this._sessionDisposables.add(new ClickLinkGesture(this._editor)); - this._sessionDisposables.add(gesture.onMouseMoveOrRelevantKeyDown(e => { + this._sessionDisposables.add(this._installLinkGesture()); + } + + private _installLinkGesture(): IDisposable { + + let removeHighlight = () => { }; + const gesture = new ClickLinkGesture(this._editor); + + gesture.onMouseMoveOrRelevantKeyDown(e => { const [mouseEvent] = e; if (mouseEvent.target.type !== MouseTargetType.CONTENT_TEXT || typeof mouseEvent.target.detail !== 'object' || !mouseEvent.hasTriggerModifier) { - undoHover(); + removeHighlight(); return; } const model = this._editor.getModel()!; @@ -165,14 +168,14 @@ export class InlayHintsController implements IEditorContribution { } } this._updateHintsDecorators([range], Array.from(lineHints)); - undoHover = () => { + removeHighlight = () => { this._activeInlayHintPart = undefined; this._updateHintsDecorators([range], Array.from(lineHints)); }; } - })); - this._sessionDisposables.add(gesture.onCancel(undoHover)); - this._sessionDisposables.add(gesture.onExecute(e => { + }); + gesture.onCancel(removeHighlight); + gesture.onExecute(e => { if (e.target.type !== MouseTargetType.CONTENT_TEXT || typeof e.target.detail !== 'object' || !e.hasTriggerModifier) { return; } @@ -180,7 +183,26 @@ export class InlayHintsController implements IEditorContribution { if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { this._openerService.open(options.attachedData.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); } - })); + }); + return gesture; + } + + private _cacheHintsForFastRestore(model: ITextModel): void { + const items = new Set(); + for (const [id, obj] of this._decorationsMetadata) { + if (items.has(obj.item)) { + // an inlay item can be rendered as multiple decorations + // but they will all uses the same range + continue; + } + items.add(obj.item); + const range = model.getDecorationRange(id); + if (range) { + // update range with whatever the editor has tweaked it to + obj.item.anchor.range = range; + } + } + this._cache.set(model, Array.from(items)); } private _getHintsRanges(): Range[] { @@ -202,8 +224,6 @@ export class InlayHintsController implements IEditorContribution { private _updateHintsDecorators(ranges: Range[], items: readonly InlayHintItem[]): void { const { fontSize, fontFamily } = this._getLayoutInfo(); - const model = this._editor.getModel()!; - const newDecorationsData: { item: InlayHintItem, decoration: IModelDeltaDecoration, classNameRef: IDisposable }[] = []; const fontFamilyVar = '--code-editorInlayHintsFontFamily'; @@ -211,28 +231,10 @@ export class InlayHintsController implements IEditorContribution { for (const item of items) { - const { position, whitespaceBefore, whitespaceAfter } = item.hint; - - // position - let direction: 'before' | 'after' = 'before'; - let range = Range.fromPositions(position); - let word = model.getWordAtPosition(position); - let usesWordRange = false; - if (word) { - if (word.endColumn === position.column) { - direction = 'after'; - usesWordRange = true; - range = wordToRange(word, position.lineNumber); - } else if (word.startColumn === position.column) { - usesWordRange = true; - range = wordToRange(word, position.lineNumber); - } - } - // text w/ links const { nodes } = parseLinkedText(item.hint.label); - const marginBefore = whitespaceBefore ? (fontSize / 3) | 0 : 0; - const marginAfter = whitespaceAfter ? (fontSize / 3) | 0 : 0; + const marginBefore = item.hint.whitespaceBefore ? (fontSize / 3) | 0 : 0; + const marginAfter = item.hint.whitespaceAfter ? (fontSize / 3) | 0 : 0; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; @@ -281,19 +283,19 @@ export class InlayHintsController implements IEditorContribution { const classNameRef = this._ruleFactory.createClassNameRef(cssProperties); newDecorationsData.push({ - item: item, + item, classNameRef, decoration: { - range, + range: item.anchor.range, options: { - [direction]: { + [item.anchor.direction]: { content: fixSpace(isLink ? node.label : node), inlineClassNameAffectsLetterSpacing: true, inlineClassName: classNameRef.className, attachedData: new InlayHintLabelPart(item, i, isLink ? node.href : undefined) } as InjectedTextOptions, description: 'InlayHint', - showIfCollapsed: !usesWordRange, + showIfCollapsed: !item.anchor.usesWordRange, stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges } }, @@ -359,14 +361,7 @@ export class InlayHintsController implements IEditorContribution { } } -function wordToRange(word: IWordAtPosition, lineNumber: number): Range { - return new Range( - lineNumber, - word.startColumn, - lineNumber, - word.endColumn - ); -} + // Prevents the view from potentially visible whitespace function fixSpace(str: string): string { From 231e283c8cbebbe7f24a9acb467b4e3d0d51b535 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 11:45:11 -0600 Subject: [PATCH 1072/2210] enableWebglRenderer within xtermTerminal --- .../workbench/contrib/terminal/browser/terminal.ts | 5 ----- .../contrib/terminal/browser/terminalInstance.ts | 5 +---- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 13 ++++++++++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d46a298916216..a273a9f8551de 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -839,11 +839,6 @@ export interface IXtermTerminal { * viewport. */ clearBuffer(): void; - - /* - * Enables the webgl renderer - */ - enableWebglRenderer(): Promise } export interface IRequestAddInstanceToGroupEvent { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index ad4c2be84a0e5..837be3290c9be 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -66,7 +66,7 @@ import { LineDataEventAddon } from 'vs/workbench/contrib/terminal/browser/xterm/ import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; -import { isFirefox, isSafari } from 'vs/base/browser/browser'; +import { isFirefox } from 'vs/base/browser/browser'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; const enum Constants { @@ -626,9 +626,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._linkManager = this._instantiationService.createInstance(TerminalLinkManager, xterm.raw, this._processManager!); this._areLinksReady = true; this._onLinksReady.fire(this); - if ((!isSafari && this._configHelper.config.gpuAcceleration === 'auto') || this._configHelper.config.gpuAcceleration === 'on') { - await xterm.enableWebglRenderer(); - } }); this._loadTypeAheadAddon(xterm); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 1f77f27ccd379..ce036d4174c0c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -151,6 +151,9 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this.raw.open(container); } this._container = container; + if (this._shouldLoadWebgl()) { + this._enableWebglRenderer(); + } } updateConfig(): void { @@ -171,14 +174,18 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this.raw.options.rightClickSelectsWord = config.rightClickBehavior === 'selectWord'; this.raw.options.wordSeparator = config.wordSeparators; this.raw.options.customGlyphs = config.customGlyphs; - if ((!isSafari && config.gpuAcceleration === 'auto' && XtermTerminal._suggestedRendererType === undefined) || config.gpuAcceleration === 'on') { - this.enableWebglRenderer(); + if (this._shouldLoadWebgl()) { + this._enableWebglRenderer(); } else { this._disposeOfWebglRenderer(); this.raw.options.rendererType = this._getBuiltInXtermRenderer(config.gpuAcceleration, XtermTerminal._suggestedRendererType); } } + private _shouldLoadWebgl(): boolean { + return !isSafari && (this._configHelper.config.gpuAcceleration === 'auto' && XtermTerminal._suggestedRendererType === undefined) || this._configHelper.config.gpuAcceleration === 'on'; + } + forceRedraw() { this._webglAddon?.clearTextureAtlas(); this.raw.clearTextureAtlas(); @@ -312,7 +319,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { return rendererType; } - async enableWebglRenderer(): Promise { + private async _enableWebglRenderer(): Promise { if (!this.raw.element || this._webglAddon) { return; } From 1b48d7624fb462e24bc9841833d5ca219ce3eab6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 13:13:53 -0600 Subject: [PATCH 1073/2210] add abstract command tracker --- .../terminal/browser/terminalInstance.ts | 2 +- .../xterm/cognisantCommandTrackerAddon.ts | 66 --------- .../browser/xterm/commandTrackerAddon.ts | 130 +++++++++++++----- .../terminal/browser/xterm/xtermTerminal.ts | 5 +- .../browser/terminalCommandTracker.test.ts | 4 +- 5 files changed, 103 insertions(+), 104 deletions(-) delete mode 100644 src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 2c098699d28b3..a5ddfd45707ff 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -68,8 +68,8 @@ import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnviro import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { isFirefox, isSafari } from 'vs/base/browser/browser'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; -import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon'; import { getTimeSinceCommand } from 'vs/workbench/contrib/terminal/browser/terminalTime'; +import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; const enum Constants { /** diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts deleted file mode 100644 index f4ab0def0b6f9..0000000000000 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter } from 'vs/base/common/event'; -import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; -import { getCurrentTimestamp } from 'vs/workbench/contrib/terminal/browser/terminalTime'; -import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; -import { Terminal } from 'xterm'; - -export class CognisantCommandTrackerAddon extends CommandTrackerAddon { - private _dataIsCommand = false; - private _commands: TerminalCommand[] = []; - private _exitCode: number | undefined; - private _cwd: string | undefined; - private _currentCommand = ''; - - private readonly _onCwdChanged = new Emitter(); - readonly onCwdChanged = this._onCwdChanged.event; - - override activate(terminal: Terminal): void { - terminal.onData(data => { - if (this._dataIsCommand) { - this._currentCommand += data; - } - }); - } - - override handleIntegratedShellChange(event: { type: string, value: string }): void { - switch (event.type) { - case ShellIntegrationInfo.CurrentDir: - this._cwd = event.value; - this._onCwdChanged.fire(this._cwd); - break; - case ShellIntegrationInfo.RemoteHost: - break; - case ShellIntegrationInteraction.PromptStart: - break; - case ShellIntegrationInteraction.CommandStart: - this._dataIsCommand = true; - break; - case ShellIntegrationInteraction.CommandExecuted: - break; - case ShellIntegrationInteraction.CommandFinished: - this._exitCode = Number.parseInt(event.value); - if (!this._currentCommand.startsWith('\\') && this._currentCommand !== '') { - this._commands.push( - { - command: this._currentCommand, - timestamp: getCurrentTimestamp(), - cwd: this._cwd, - exitCode: this._exitCode - }); - } - this._currentCommand = ''; - break; - default: - return; - } - } - - override getCommands(): TerminalCommand[] { - return this._commands; - } -} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index f32c01ed57ecb..5c50d58912ce5 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -5,7 +5,9 @@ import type { Terminal, IMarker, ITerminalAddon } from 'xterm'; import { ICommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalCommand } from 'vs/platform/terminal/common/terminal'; +import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; +import { getCurrentTimestamp } from 'vs/workbench/contrib/terminal/browser/terminalTime'; +import { Emitter } from 'vs/base/common/event'; /** * The minimum size of the prompt in which to assume the line is a command. @@ -22,20 +24,15 @@ export const enum ScrollPosition { Middle } -export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { +export abstract class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { private _currentMarker: IMarker | Boundary = Boundary.Bottom; private _selectionStart: IMarker | Boundary | null = null; private _isDisposable: boolean = false; - private _terminal: Terminal | undefined; + abstract _terminal: Terminal | undefined; - getCommands(): TerminalCommand[] { - return []; - } - - activate(terminal: Terminal): void { - this._terminal = terminal; - terminal.onKey(e => this._onKey(e.key)); - } + abstract getCommands(): TerminalCommand[]; + abstract activate(terminal: Terminal): void; + abstract handleIntegratedShellChange(event: { type: string, value: string }): void; dispose(): void { } @@ -47,27 +44,6 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { this._selectionStart = null; } - handleIntegratedShellChange(event: { type: string, value: string }): void { - - } - - private _onKey(key: string): void { - if (key === '\x0d') { - this._onEnter(); - } - - this.clearMarker(); - } - - private _onEnter(): void { - if (!this._terminal) { - return; - } - if (this._terminal.buffer.active.cursorX >= MINIMUM_PROMPT_LENGTH) { - this._terminal.registerMarker(0); - } - } - scrollToPreviousCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { if (!this._terminal) { return; @@ -335,3 +311,93 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { return xterm.markers.length; } } + +export class NaiveCommandTrackerAddon extends CommandTrackerAddon { + _terminal: Terminal | undefined; + getCommands(): TerminalCommand[] { + return []; + } + + activate(terminal: Terminal): void { + this._terminal = terminal; + terminal.onKey(e => this._onKey(e.key)); + } + + private _onKey(key: string): void { + if (key === '\x0d') { + this._onEnter(); + } + + this.clearMarker(); + } + + private _onEnter(): void { + if (!this._terminal) { + return; + } + if (this._terminal.buffer.active.cursorX >= MINIMUM_PROMPT_LENGTH) { + this._terminal.registerMarker(0); + } + } + + handleIntegratedShellChange(event: { type: string; value: string; }): void { + throw new Error('Integrated shell change not supported'); + } +} + +export class CognisantCommandTrackerAddon extends CommandTrackerAddon { + _terminal: Terminal | undefined; + private _dataIsCommand = false; + private _commands: TerminalCommand[] = []; + private _exitCode: number | undefined; + private _cwd: string | undefined; + private _currentCommand = ''; + + private readonly _onCwdChanged = new Emitter(); + readonly onCwdChanged = this._onCwdChanged.event; + + activate(terminal: Terminal): void { + terminal.onData(data => { + if (this._dataIsCommand) { + this._currentCommand += data; + } + }); + } + + handleIntegratedShellChange(event: { type: string, value: string }): void { + switch (event.type) { + case ShellIntegrationInfo.CurrentDir: + this._cwd = event.value; + this._onCwdChanged.fire(this._cwd); + break; + case ShellIntegrationInfo.RemoteHost: + break; + case ShellIntegrationInteraction.PromptStart: + break; + case ShellIntegrationInteraction.CommandStart: + this._dataIsCommand = true; + break; + case ShellIntegrationInteraction.CommandExecuted: + break; + case ShellIntegrationInteraction.CommandFinished: + this._exitCode = Number.parseInt(event.value); + if (!this._currentCommand.startsWith('\\') && this._currentCommand !== '') { + this._commands.push( + { + command: this._currentCommand, + timestamp: getCurrentTimestamp(), + cwd: this._cwd, + exitCode: this._exitCode + }); + } + this._currentCommand = ''; + break; + default: + return; + } + } + + getCommands(): TerminalCommand[] { + return this._commands; + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 93a8749eeaf2d..415b7e2be19f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -20,7 +20,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; +import { CognisantCommandTrackerAddon, CommandTrackerAddon, NaiveCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { localize } from 'vs/nls'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; @@ -28,7 +28,6 @@ import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TERMINAL_FOREGROUND_COLOR, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR, ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { Color } from 'vs/base/common/color'; -import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -143,7 +142,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Load addons this._updateUnicodeVersion(); - this._commandTrackerAddon = new CommandTrackerAddon(); + this._commandTrackerAddon = new NaiveCommandTrackerAddon(); this.raw.loadAddon(this._commandTrackerAddon); } diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts index 27b06adecd6e6..db169b7dc35cb 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalCommandTracker.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Terminal } from 'xterm'; -import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; +import { CommandTrackerAddon, NaiveCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { isWindows } from 'vs/base/common/platform'; import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; import { timeout } from 'vs/base/common/async'; @@ -48,7 +48,7 @@ suite('Workbench - TerminalCommandTracker', function () { data += `${i}\n`; } await writeP(xterm, data); - commandTracker = new CommandTrackerAddon(); + commandTracker = new NaiveCommandTrackerAddon(); xterm.loadAddon(commandTracker); }); From 58c7af5349037a01a86fbd68c7cea44c137f1bdc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 13:23:18 -0600 Subject: [PATCH 1074/2210] on restore, restart shell integration --- src/vs/platform/terminal/node/ptyService.ts | 48 ++++++++------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 12c5e64179549..a0cf75d93583f 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -15,7 +15,7 @@ import { RequestStore } from 'vs/platform/terminal/common/requestStore'; import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, IFixedTerminalDimensions, ProcessCapability, IPersistentTerminalProcessLaunchOptions, ICrossVersionSerializedTerminalState, ISerializedTerminalState } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; -import { ITerminalAddon, Terminal as XtermTerminal } from 'xterm-headless'; +import { Terminal as XtermTerminal } from 'xterm-headless'; import type { ISerializeOptions, SerializeAddon as XtermSerializeAddon } from 'xterm-addon-serialize'; import type { Unicode11Addon as XtermUnicode11Addon } from 'xterm-addon-unicode11'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto } from 'vs/platform/terminal/common/terminalProcess'; @@ -201,11 +201,7 @@ export class PtyService extends Disposable implements IPtyService { persistentProcess.installAutoReply(e[0], e[1]); } }); - const shellIntegrationAddon = new ShellIntegrationAddon(); - shellIntegrationAddon.onDidStartShellIntegration(() => { - this._logService.info('updating shell'); - process.updateProperty(ProcessPropertyType.Capability, ProcessCapability.CommandCognisant); - }); + this._ptys.set(id, persistentProcess); return id; } @@ -735,28 +731,13 @@ export class PersistentTerminalProcess extends Disposable { } } -class ShellIntegrationAddon extends Disposable implements ITerminalAddon { - private readonly _onDidStartShellIntegration = this._register(new Emitter()); - readonly onDidStartShellIntegration = this._onDidStartShellIntegration.event; - activate(terminal: XtermTerminal): void { - terminal.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); - } - private _handleShellIntegration(data: string): boolean { - const [command,] = data.split(';'); - switch (command) { - case 'E': - this._onDidStartShellIntegration.fire(); - default: - return false; - } - } -} - class XtermSerializer implements ITerminalSerializer { private _xterm: XtermTerminal; private _unicodeAddon?: XtermUnicode11Addon; private readonly _onDidStartShellIntegration = new Emitter(); readonly onDidStartShellIntegration = this._onDidStartShellIntegration.event; + private _shellIntegration: boolean | undefined; + constructor( cols: number, rows: number, @@ -768,15 +749,24 @@ class XtermSerializer implements ITerminalSerializer { if (reviveBuffer) { this._xterm.writeln(reviveBuffer); } - const shellIntegrationAddon = new ShellIntegrationAddon(); - shellIntegrationAddon.activate(this._xterm); - shellIntegrationAddon.onDidStartShellIntegration(() => { - this._onDidStartShellIntegration.fire(); - }); - this._xterm.loadAddon(new ShellIntegrationAddon()); + this._xterm.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); + if (this._shellIntegration) { + this._xterm.writeln('\033]133;E\007'); + } this.setUnicodeVersion(unicodeVersion); } + private _handleShellIntegration(data: string): boolean { + const [command,] = data.split(';'); + switch (command) { + case 'E': + this._shellIntegration = true; + this._onDidStartShellIntegration.fire(); + default: + return false; + } + } + handleData(data: string): void { this._xterm.write(data); } From 8b423f0a049779cc85c52871a6292b499610b2b0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 13:27:18 -0600 Subject: [PATCH 1075/2210] use time from common --- src/vs/platform/terminal/common/terminal.ts | 2 +- .../terminal/browser/terminalInstance.ts | 6 ++-- .../contrib/terminal/browser/terminalTime.ts | 32 ------------------- .../browser/xterm/commandTrackerAddon.ts | 3 +- 4 files changed, 5 insertions(+), 38 deletions(-) delete mode 100644 src/vs/workbench/contrib/terminal/browser/terminalTime.ts diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 83635ea4ea2f5..1969fb5eb82a7 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -374,7 +374,7 @@ export interface IHeartbeatService { export interface TerminalCommand { command: string; - timestamp: string; + timestamp: number; cwd?: string; exitCode?: number; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index a5ddfd45707ff..4a7bf0c4d720a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -68,8 +68,8 @@ import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnviro import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { isFirefox, isSafari } from 'vs/base/browser/browser'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; -import { getTimeSinceCommand } from 'vs/workbench/contrib/terminal/browser/terminalTime'; import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; +import { fromNow } from 'vs/base/common/date'; const enum Constants { /** @@ -721,8 +721,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { items.push({ label, description: exitCodeDescription + cwdDescription, - detail: getTimeSinceCommand(timestamp), - id: timestamp + detail: fromNow(timestamp), + id: timestamp.toString() }); } } else { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTime.ts b/src/vs/workbench/contrib/terminal/browser/terminalTime.ts deleted file mode 100644 index dd41031c5f0a1..0000000000000 --- a/src/vs/workbench/contrib/terminal/browser/terminalTime.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function getCurrentTimestamp(): string { - const currentTime = new Date(); - return `${currentTime.getMonth() + 1}-${currentTime.getDate()}-${currentTime.getHours()}-${currentTime.getMinutes()}-${currentTime.getSeconds()}-${currentTime.getMilliseconds()} `; -} -export function getTimeSinceCommand(timeOfCommand: string): string { - const timeNow = getCurrentTimestamp(); - const now = timeNow.split('-'); - const command = timeOfCommand.split('-'); - let i = 0; - while (now[i] === command[i] && i < command.length) { - i++; - } - const amount = Number.parseInt(now[i]) - Number.parseInt(command[i]); - switch (i) { - case 0: - return `${amount} months ago`; - case 1: - return `${amount} days ago`; - case 2: - return `${amount} hours ago`; - case 3: - return `${amount} minutes ago`; - case 4: - return `${amount} seconds ago`; - } - return 'a long time ago'; -} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index 5c50d58912ce5..b832c2448f789 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -6,7 +6,6 @@ import type { Terminal, IMarker, ITerminalAddon } from 'xterm'; import { ICommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; -import { getCurrentTimestamp } from 'vs/workbench/contrib/terminal/browser/terminalTime'; import { Emitter } from 'vs/base/common/event'; /** @@ -385,7 +384,7 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { this._commands.push( { command: this._currentCommand, - timestamp: getCurrentTimestamp(), + timestamp: new Date().getTime(), cwd: this._cwd, exitCode: this._exitCode }); From 01112a138c3fbdb4e1818efecb12d9382c9a7491 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 13:31:28 -0600 Subject: [PATCH 1076/2210] fix errors --- src/vs/platform/terminal/node/ptyService.ts | 2 +- .../contrib/terminal/browser/xterm/commandTrackerAddon.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index a0cf75d93583f..dcc9ef36e71d0 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -751,7 +751,7 @@ class XtermSerializer implements ITerminalSerializer { } this._xterm.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); if (this._shellIntegration) { - this._xterm.writeln('\033]133;E\007'); + this._xterm.writeln('\x1b033]133;E\x1b007'); } this.setUnicodeVersion(unicodeVersion); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index b832c2448f789..17b113d60d770 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -340,7 +340,6 @@ export class NaiveCommandTrackerAddon extends CommandTrackerAddon { } handleIntegratedShellChange(event: { type: string; value: string; }): void { - throw new Error('Integrated shell change not supported'); } } From 5efae8242cb041acaba138747a3fcde258f1da9a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 13:37:22 -0600 Subject: [PATCH 1077/2210] add ago --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 4a7bf0c4d720a..41d9e41ea40d8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -721,7 +721,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { items.push({ label, description: exitCodeDescription + cwdDescription, - detail: fromNow(timestamp), + detail: fromNow(timestamp) === 'now' ? 'now' : `${fromNow(timestamp)} ago`, id: timestamp.toString() }); } From 768bf940e0c217510e642e86cd1dd04bf7c7433e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 13:47:12 -0600 Subject: [PATCH 1078/2210] merge --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 8b74a5200e734..1bd17ed459b12 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -847,11 +847,6 @@ export interface IXtermTerminal { clearBuffer(): void; <<<<<<< HEAD - /* - * Enables the webgl renderer - */ - enableWebglRenderer(): Promise - /* * When process capabilites are updated, update the command tracker */ From eb18795e5e11f71a5f3f1ac3f46ff79d3c41f48c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 13:48:20 -0600 Subject: [PATCH 1079/2210] fix merge issue --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 1bd17ed459b12..a0836949d50ad 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -845,14 +845,11 @@ export interface IXtermTerminal { * viewport. */ clearBuffer(): void; -<<<<<<< HEAD /* * When process capabilites are updated, update the command tracker */ upgradeCommandTracker(): void; -======= ->>>>>>> main } export interface IRequestAddInstanceToGroupEvent { From 384cee8d17a65bab2d0a9bf1846eb2eb62551190 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Thu, 6 Jan 2022 12:00:57 -0800 Subject: [PATCH 1080/2210] Properly prevent same-page navigation Ref #138175 --- src/vs/workbench/contrib/preferences/browser/settingsTree.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 08e2cb30c135f..1ef6c2ceab1c4 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1903,8 +1903,9 @@ export class SettingUntrustedRenderer extends AbstractSettingRenderer implements linkElement.setAttribute('tabindex', '0'); linkElement.href = '#'; template.toDispose.add(DOM.addStandardDisposableListener(linkElement, DOM.EventType.CLICK, (e: MouseEvent) => { - this._commandService.executeCommand('workbench.trust.manage'); + e.preventDefault(); e.stopPropagation(); + this._commandService.executeCommand('workbench.trust.manage'); })); template.toDispose.add(DOM.addStandardDisposableListener(linkElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { From a96e9fd8f6280360859260791a1b3756b2360d27 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 14:40:17 -0600 Subject: [PATCH 1081/2210] use marker instead of listening to onData to track command value --- src/vs/platform/terminal/node/ptyService.ts | 5 --- .../browser/xterm/commandTrackerAddon.ts | 36 +++++++++---------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index dcc9ef36e71d0..eebf84c833673 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -569,11 +569,6 @@ export class PersistentTerminalProcess extends Disposable { async updateProperty(type: T, value: IProcessPropertyMap[T]): Promise { if (type === ProcessPropertyType.FixedDimensions) { return this._setFixedDimensions(value as IProcessPropertyMap[ProcessPropertyType.FixedDimensions]); - } else if (type === ProcessPropertyType.Capability) { - //TODO:? - // // if (!this.capabilities.find(c => c === value) && value) { - // this.capabilities.push(value); - // } } } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index 17b113d60d770..938728ba44473 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -221,13 +221,13 @@ export abstract class CommandTrackerAddon implements ICommandTracker, ITerminalA } if (this._currentMarker === Boundary.Bottom) { - this._currentMarker = this._addMarkerOrThrow(xterm, this._getOffset(xterm) - 1); + this._currentMarker = this._registerMarkerOrThrow(xterm, this._getOffset(xterm) - 1); } else { const offset = this._getOffset(xterm); if (this._isDisposable) { this._currentMarker.dispose(); } - this._currentMarker = this._addMarkerOrThrow(xterm, offset - 1); + this._currentMarker = this._registerMarkerOrThrow(xterm, offset - 1); } this._isDisposable = true; this._scrollToMarker(this._currentMarker, scrollPosition); @@ -244,20 +244,20 @@ export abstract class CommandTrackerAddon implements ICommandTracker, ITerminalA } if (this._currentMarker === Boundary.Top) { - this._currentMarker = this._addMarkerOrThrow(xterm, this._getOffset(xterm) + 1); + this._currentMarker = this._registerMarkerOrThrow(xterm, this._getOffset(xterm) + 1); } else { const offset = this._getOffset(xterm); if (this._isDisposable) { this._currentMarker.dispose(); } - this._currentMarker = this._addMarkerOrThrow(xterm, offset + 1); + this._currentMarker = this._registerMarkerOrThrow(xterm, offset + 1); } this._isDisposable = true; this._scrollToMarker(this._currentMarker, scrollPosition); } - private _addMarkerOrThrow(xterm: Terminal, cursorYOffset: number): IMarker { - const marker = xterm.addMarker(cursorYOffset); + private _registerMarkerOrThrow(xterm: Terminal, cursorYOffset: number): IMarker { + const marker = xterm.registerMarker(cursorYOffset); if (!marker) { throw new Error(`Could not create marker for ${cursorYOffset}`); } @@ -345,21 +345,16 @@ export class NaiveCommandTrackerAddon extends CommandTrackerAddon { export class CognisantCommandTrackerAddon extends CommandTrackerAddon { _terminal: Terminal | undefined; - private _dataIsCommand = false; private _commands: TerminalCommand[] = []; private _exitCode: number | undefined; private _cwd: string | undefined; - private _currentCommand = ''; - + private _commandMarker: IMarker | undefined; + private _commandCharStart: number | undefined; private readonly _onCwdChanged = new Emitter(); readonly onCwdChanged = this._onCwdChanged.event; activate(terminal: Terminal): void { - terminal.onData(data => { - if (this._dataIsCommand) { - this._currentCommand += data; - } - }); + this._terminal = terminal; } handleIntegratedShellChange(event: { type: string, value: string }): void { @@ -373,22 +368,27 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { case ShellIntegrationInteraction.PromptStart: break; case ShellIntegrationInteraction.CommandStart: - this._dataIsCommand = true; + this._commandMarker = this._terminal?.registerMarker(0); + this._commandCharStart = this._terminal?.buffer.active.cursorX; break; case ShellIntegrationInteraction.CommandExecuted: break; case ShellIntegrationInteraction.CommandFinished: this._exitCode = Number.parseInt(event.value); - if (!this._currentCommand.startsWith('\\') && this._currentCommand !== '') { + if (!this._commandMarker?.line || !this._terminal?.buffer.active) { + break; + } + // eslint-disable-next-line no-case-declarations + const command = this._terminal.buffer.active.getLine(this._commandMarker.line)?.translateToString().substring(this._commandCharStart || 0); + if (command && !command.startsWith('\\') && command !== '') { this._commands.push( { - command: this._currentCommand, + command, timestamp: new Date().getTime(), cwd: this._cwd, exitCode: this._exitCode }); } - this._currentCommand = ''; break; default: return; From 969b065b27a6c33c5695a33753a96be1689d03e2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 14:48:02 -0600 Subject: [PATCH 1082/2210] clean up --- src/vs/platform/terminal/common/terminal.ts | 1 - src/vs/platform/terminal/node/ptyService.ts | 2 +- .../contrib/terminal/browser/xterm/commandTrackerAddon.ts | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 2b9ba30892a22..887c7820d785e 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -380,7 +380,6 @@ export interface TerminalCommand { } export enum ShellIntegrationInfo { - RemoteHost = 'RemoteHost', CurrentDir = 'CurrentDir', } diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index eebf84c833673..73ff18f32e239 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -738,7 +738,7 @@ class XtermSerializer implements ITerminalSerializer { rows: number, scrollback: number, unicodeVersion: '6' | '11', - reviveBuffer: string | undefined, + reviveBuffer: string | undefined ) { this._xterm = new XtermTerminal({ cols, rows, scrollback }); if (reviveBuffer) { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index 938728ba44473..feface491761c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -363,8 +363,6 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { this._cwd = event.value; this._onCwdChanged.fire(this._cwd); break; - case ShellIntegrationInfo.RemoteHost: - break; case ShellIntegrationInteraction.PromptStart: break; case ShellIntegrationInteraction.CommandStart: From a87cdeaec7478781729fb3ab2e713ef5f6a1f395 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 6 Jan 2022 13:02:38 -0800 Subject: [PATCH 1083/2210] testing: add refresh action For #139737 --- src/vs/base/common/arrays.ts | 8 +-- .../api/browser/mainThreadTesting.ts | 25 +++++++-- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 11 +++- src/vs/workbench/api/common/extHostTesting.ts | 21 +++++-- .../contrib/testing/browser/icons.ts | 1 + .../testing/browser/testExplorerActions.ts | 56 +++++++++++++++++++ .../testing/browser/testingExplorerView.ts | 19 +++++-- .../contrib/testing/common/testId.ts | 8 +++ .../contrib/testing/common/testService.ts | 12 ++++ .../contrib/testing/common/testServiceImpl.ts | 39 ++++++++++++- .../testing/common/testingContextKeys.ts | 1 + .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.testRefresh.d.ts | 37 ++++++++++++ 14 files changed, 215 insertions(+), 28 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.testRefresh.d.ts diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 5a5bbf31cc2f7..0a66387f97b9b 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -339,17 +339,17 @@ export function distinct(array: ReadonlyArray, keyFn: (value: T) => any = }); } -export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { - const seen: { [key: string]: boolean; } = Object.create(null); +export function uniqueFilter(keyFn: (t: T) => R): (t: T) => boolean { + const seen = new Set(); return element => { const key = keyFn(element); - if (seen[key]) { + if (seen.has(key)) { return false; } - seen[key] = true; + seen.add(key); return true; }; } diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index c2c8ac5805b24..b18fb3a1b36bc 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -17,7 +17,7 @@ import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { IMainThreadTestController, ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService'; -import { ExtHostContext, ExtHostTestingShape, IExtHostContext, ILocationDto, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostTestingShape, IExtHostContext, ILocationDto, ITestControllerPatch, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; const reviveDiff = (diff: TestsDiff) => { for (const entry of diff) { @@ -40,6 +40,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh private readonly testProviderRegistrations = new Map; + canRefresh: MutableObservableValue; disposable: IDisposable }>(); @@ -193,12 +194,15 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh /** * @inheritdoc */ - public $registerTestController(controllerId: string, labelStr: string) { + public $registerTestController(controllerId: string, labelStr: string, canRefreshValue: boolean) { const disposable = new DisposableStore(); - const label = new MutableObservableValue(labelStr); + const label = disposable.add(new MutableObservableValue(labelStr)); + const canRefresh = disposable.add(new MutableObservableValue(canRefreshValue)); const controller: IMainThreadTestController = { id: controllerId, label, + canRefresh, + refreshTests: () => this.proxy.$refreshTests(controllerId), configureRunProfile: id => this.proxy.$configureRunProfile(controllerId, id), runTests: (req, token) => this.proxy.$runControllerTests(req, token), expandTest: (testId, levels) => this.proxy.$expandTest(testId, isFinite(levels) ? levels : -1), @@ -211,6 +215,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh this.testProviderRegistrations.set(controllerId, { instance: controller, label, + canRefresh, disposable }); } @@ -218,10 +223,18 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh /** * @inheritdoc */ - public $updateControllerLabel(controllerId: string, label: string) { + public $updateController(controllerId: string, patch: ITestControllerPatch) { const controller = this.testProviderRegistrations.get(controllerId); - if (controller) { - controller.label.value = label; + if (!controller) { + return; + } + + if (patch.label !== undefined) { + controller.label.value = patch.label; + } + + if (patch.canRefresh !== undefined) { + controller.canRefresh.value = patch.canRefresh; } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index eba793093fa66..a10fe9f4fcbd9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -354,8 +354,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I : extHostTypes.ExtensionKind.UI; const tests: typeof vscode.tests = { - createTestController(provider, label) { - return extHostTesting.createTestController(provider, label); + createTestController(provider, label, refreshHandler?: () => Thenable | void) { + return extHostTesting.createTestController(provider, label, refreshHandler); }, createTestObserver() { checkProposedApiEnabled(extension, 'testObserver'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d5a048e8ae524..3f8407431ac30 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2159,15 +2159,22 @@ export interface ExtHostTestingShape { $resolveFileCoverage(runId: string, taskId: string, fileIndex: number, token: CancellationToken): Promise; /** Configures a test run config. */ $configureRunProfile(controllerId: string, configId: number): void; + /** Asks the controller to refresh its tests */ + $refreshTests(controllerId: string): Promise; +} + +export interface ITestControllerPatch { + label?: string; + canRefresh?: boolean; } export interface MainThreadTestingShape { // --- test lifecycle: /** Registers that there's a test controller with the given ID */ - $registerTestController(controllerId: string, label: string): void; + $registerTestController(controllerId: string, label: string, canRefresh: boolean): void; /** Updates the label of an existing test controller. */ - $updateControllerLabel(controllerId: string, label: string): void; + $updateController(controllerId: string, patch: ITestControllerPatch): void; /** Diposes of the test controller with the given ID */ $unregisterTestController(controllerId: string): void; /** Requests tests published to VS Code. */ diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 78a0901c0b681..6cf94231eb402 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -55,7 +55,7 @@ export class ExtHostTesting implements ExtHostTestingShape { /** * Implements vscode.test.registerTestProvider */ - public createTestController(controllerId: string, label: string): vscode.TestController { + public createTestController(controllerId: string, label: string, refreshHandler?: () => Thenable | void): vscode.TestController { if (this.controllers.has(controllerId)) { throw new Error(`Attempt to insert a duplicate controller with ID "${controllerId}"`); } @@ -75,7 +75,14 @@ export class ExtHostTesting implements ExtHostTestingShape { set label(value: string) { label = value; collection.root.label = value; - proxy.$updateControllerLabel(controllerId, label); + proxy.$updateController(controllerId, { label }); + }, + get refreshHandler() { + return refreshHandler; + }, + set refreshHandler(value: (() => Thenable | void) | undefined) { + refreshHandler = value; + proxy.$updateController(controllerId, { canRefresh: !!value }); }, get id() { return controllerId; @@ -109,10 +116,7 @@ export class ExtHostTesting implements ExtHostTestingShape { }, }; - // back compat: - (controller as any).createRunConfiguration = controller.createRunProfile; - - proxy.$registerTestController(controllerId, label); + proxy.$registerTestController(controllerId, label, !!refreshHandler); disposable.add(toDisposable(() => proxy.$unregisterTestController(controllerId))); const info: ControllerInfo = { controller, collection, profiles: profiles }; @@ -178,6 +182,11 @@ export class ExtHostTesting implements ExtHostTestingShape { this.controllers.get(controllerId)?.profiles.get(profileId)?.configureHandler?.(); } + /** @inheritdoc */ + async $refreshTests(controllerId: string) { + await this.controllers.get(controllerId)?.controller.refreshHandler?.(); + } + /** * Updates test results shown to extensions. * @override diff --git a/src/vs/workbench/contrib/testing/browser/icons.ts b/src/vs/workbench/contrib/testing/browser/icons.ts index 6928994dfdf36..82301b24cb58c 100644 --- a/src/vs/workbench/contrib/testing/browser/icons.ts +++ b/src/vs/workbench/contrib/testing/browser/icons.ts @@ -25,6 +25,7 @@ export const testingShowAsList = registerIcon('testing-show-as-list-icon', Codic export const testingShowAsTree = registerIcon('testing-show-as-list-icon', Codicon.listFlat, localize('testingShowAsTree', 'Icon shown when the test explorer is disabled as a list.')); export const testingUpdateProfiles = registerIcon('testing-update-profiles', Codicon.gear, localize('testingUpdateProfiles', 'Icon shown to update test profiles.')); +export const testingRefreshTests = registerIcon('testing-refresh-tests', Codicon.refresh, localize('testingRefreshTests', 'Icon on the button to refresh tests.')); export const testingStatesToIcons = new Map([ [TestResultState.Errored, registerIcon('testing-error-icon', Codicon.issues, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))], diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 8d857dd102e4d..8ba3ef4be5672 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; import { Iterable } from 'vs/base/common/iterator'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -1143,6 +1144,60 @@ export class ToggleInlineTestOutput extends Action2 { } } +export class RefreshTestsAction extends Action2 { + public static readonly ID = 'testing.refreshTests'; + constructor() { + super({ + id: RefreshTestsAction.ID, + title: localize('testing.refreshTests', "Refresh Tests"), + category, + icon: icons.testingRefreshTests, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.Semicolon, KeyCode.KeyR), + }, + menu: [ + { + id: MenuId.TestItem, + group: 'inline', + when: TestingContextKeys.canRefreshTests.isEqualTo(true), + }, + { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.and( + ContextKeyExpr.equals('view', Testing.ExplorerViewId), + TestingContextKeys.canRefreshTests.isEqualTo(true), + ), + }, + { + id: MenuId.CommandPalette, + when: TestingContextKeys.canRefreshTests.isEqualTo(true), + }, + ] + }); + } + + public async run(accessor: ServicesAccessor, ...elements: IActionableTestTreeElement[]) { + const testService = accessor.get(ITestService); + const progressService = accessor.get(IProgressService); + + const controllerIds = distinct( + elements + .filter((e): e is TestItemTreeElement => e instanceof TestItemTreeElement) + .map(e => e.test.controllerId) + ); + + return progressService.withProgress({ location: Testing.ViewletId }, async () => { + if (controllerIds.length) { + await Promise.all(controllerIds.map(id => testService.refreshTests(id))); + } else { + await testService.refreshTests(); + } + }); + } +} + export const allTestActions = [ // todo: these are disabled until we figure out how we want autorun to work // AutoRunOffAction, @@ -1161,6 +1216,7 @@ export const allTestActions = [ GoToTest, HideTestAction, OpenOutputPeek, + RefreshTestsAction, ReRunFailedTests, ReRunLastRun, RunAction, diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 19f01fbcd3ed4..b771d9507f018 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -1239,11 +1239,20 @@ const getActionableElementActions = ( element: TestItemTreeElement, ) => { const test = element instanceof TestItemTreeElement ? element.test : undefined; - const contextOverlay = contextKeyService.createOverlay([ - ['view', Testing.ExplorerViewId], - [TestingContextKeys.testItemIsHidden.key, !!test && testService.excluded.contains(test)], - ...getTestItemContextOverlay(test, test ? profiles.capabilitiesForTest(test) : 0), - ]); + const contextKeys: [string, unknown][] = getTestItemContextOverlay(test, test ? profiles.capabilitiesForTest(test) : 0); + contextKeys.push(['view', Testing.ExplorerViewId]); + if (test) { + contextKeys.push([ + TestingContextKeys.canRefreshTests.key, + TestId.isRoot(test.item.extId) && testService.getTestController(test.item.extId)?.canRefresh.value + ]); + contextKeys.push([ + TestingContextKeys.testItemIsHidden.key, + testService.excluded.contains(test) + ]); + } + + const contextOverlay = contextKeyService.createOverlay(contextKeys); const menu = menuService.createMenu(MenuId.TestItem, contextOverlay); try { diff --git a/src/vs/workbench/contrib/testing/common/testId.ts b/src/vs/workbench/contrib/testing/common/testId.ts index 752c6c03347e6..ead4e82137675 100644 --- a/src/vs/workbench/contrib/testing/common/testId.ts +++ b/src/vs/workbench/contrib/testing/common/testId.ts @@ -55,6 +55,14 @@ export class TestId { return !idString.includes(TestIdPathParts.Delimiter); } + /** + * Cheaply ets whether the ID refers to the root . + */ + public static root(idString: string) { + const idx = idString.indexOf(TestIdPathParts.Delimiter); + return idx === -1 ? idString : idString.slice(0, idx); + } + /** * Creates a test ID from a serialized TestId instance. */ diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts index 1f76e12b4006e..3f933f0b03c67 100644 --- a/src/vs/workbench/contrib/testing/common/testService.ts +++ b/src/vs/workbench/contrib/testing/common/testService.ts @@ -22,6 +22,8 @@ export const ITestService = createDecorator('testService'); export interface IMainThreadTestController { readonly id: string; readonly label: IObservableValue; + readonly canRefresh: IObservableValue; + refreshTests(): Promise; configureRunProfile(profileId: number): void; expandTest(id: string, levels: number): Promise; runTests(request: RunTestForControllerRequest, token: CancellationToken): Promise; @@ -239,6 +241,16 @@ export interface ITestService { */ registerTestController(providerId: string, controller: IMainThreadTestController): IDisposable; + /** + * Gets a registered test controller by ID. + */ + getTestController(controllerId: string): IMainThreadTestController | undefined; + + /** + * Refreshes tests for the controller, or all controllers if no ID is given. + */ + refreshTests(controllerId?: string): Promise; + /** * Requests that tests be executed. */ diff --git a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts index 50e212fea551f..09d750dc16607 100644 --- a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts +++ b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts @@ -6,7 +6,8 @@ import { groupBy } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Iterable } from 'vs/base/common/iterator'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -33,6 +34,7 @@ export class TestService extends Disposable implements ITestService { private readonly willProcessDiffEmitter = new Emitter(); private readonly didProcessDiffEmitter = new Emitter(); private readonly providerCount: IContextKey; + private readonly canRefreshTests: IContextKey; /** * Cancellation for runs requested by the user being managed by the UI. * Test runs initiated by extensions are not included here. @@ -85,6 +87,7 @@ export class TestService extends Disposable implements ITestService { super(); this.excluded = instantiationService.createInstance(TestExclusions); this.providerCount = TestingContextKeys.providerCount.bindTo(contextKeyService); + this.canRefreshTests = TestingContextKeys.canRefreshTests.bindTo(contextKeyService); } /** @@ -215,14 +218,35 @@ export class TestService extends Disposable implements ITestService { this.didProcessDiffEmitter.fire(diff); } + /** + * @inheritdoc + */ + public getTestController(id: string) { + return this.testControllers.get(id); + } + + /** + * @inheritdoc + */ + public async refreshTests(controllerId?: string): Promise { + if (controllerId) { + await this.testControllers.get(controllerId)?.refreshTests(); + } else { + await Promise.all([...this.testControllers.values()].map(c => c.refreshTests())); + } + } + /** * @inheritdoc */ public registerTestController(id: string, controller: IMainThreadTestController): IDisposable { this.testControllers.set(id, controller); this.providerCount.set(this.testControllers.size); + this.updateCanRefresh(); - return toDisposable(() => { + const disposable = new DisposableStore(); + + disposable.add(toDisposable(() => { const diff: TestsDiff = []; for (const root of this.collection.rootItems) { if (root.controllerId === id) { @@ -234,8 +258,17 @@ export class TestService extends Disposable implements ITestService { if (this.testControllers.delete(id)) { this.providerCount.set(this.testControllers.size); + this.updateCanRefresh(); } - }); + })); + + disposable.add(controller.canRefresh.onDidChange(this.updateCanRefresh, this)); + + return disposable; + } + + private updateCanRefresh() { + this.canRefreshTests.set(Iterable.some(this.testControllers.values(), t => t.canRefresh.value)); } } diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index 9a6a8e93a1e4c..74471979be96c 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -10,6 +10,7 @@ import { TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCo export namespace TestingContextKeys { export const providerCount = new RawContextKey('testing.providerCount', 0); + export const canRefreshTests = new RawContextKey('testing.canRefresh', false, { type: 'boolean', description: localize('testing.canRefresh', 'Indicates whether any test controller has an attached refresh handler.') }); export const hasDebuggableTests = new RawContextKey('testing.hasDebuggableTests', false, { type: 'boolean', description: localize('testing.hasDebuggableTests', 'Indicates whether any test controller has registered a debug configuration') }); export const hasRunnableTests = new RawContextKey('testing.hasRunnableTests', false, { type: 'boolean', description: localize('testing.hasRunnableTests', 'Indicates whether any test controller has registered a run configuration') }); export const hasCoverableTests = new RawContextKey('testing.hasCoverableTests', false, { type: 'boolean', description: localize('testing.hasCoverableTests', 'Indicates whether any test controller has registered a coverage configuration') }); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index ce36019da177e..60210f50c5087 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -55,6 +55,7 @@ export const allApiProposals = Object.freeze({ terminalNameChangeEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalNameChangeEvent.d.ts', testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts', testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts', + testRefresh: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testRefresh.d.ts', textDocumentNotebook: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textDocumentNotebook.d.ts', textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', diff --git a/src/vscode-dts/vscode.proposed.testRefresh.d.ts b/src/vscode-dts/vscode.proposed.testRefresh.d.ts new file mode 100644 index 0000000000000..8b222a73e4f41 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.testRefresh.d.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/139737 + + export interface TestController { + /** + * If this method is present, a refresh button will be present in the + * UI, and this method will be invoked when it's clicked. When called, + * the extension should scan the workspace for any new, changed, or + * removed tests. + * + * It's recommended that extensions try to update tests in realtime, using + * a {@link FileWatcher} for example, and use this method as a fallback. + * + * @returns A thenable that resolves when tests have been refreshed. + */ + refreshHandler: (() => Thenable | void) | undefined; + } + + + export namespace tests { + /** + * Creates a new test controller. + * + * @param id Identifier for the controller, must be globally unique. + * @param label A human-readable label for the controller. + * @param refreshHandler A value for {@link TestController.refreshHandler} + * @returns An instance of the {@link TestController}. + */ + export function createTestController(id: string, label: string, refreshHandler?: () => Thenable | void): TestController; + } +} From 252b7ac7eb546116ef1e172e1d1e2b6f5162e5fb Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Thu, 6 Jan 2022 19:24:57 +0100 Subject: [PATCH 1084/2210] use DAP modules from @vscode org --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index ac67f7ffbee6d..52c0b0f3ad2f5 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", "@parcel/watcher": "2.0.5", + "@vscode/debugprotocol": "1.51.0", "@vscode/sqlite3": "4.0.12", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", @@ -202,7 +203,6 @@ "util": "^0.12.4", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", - "vscode-debugprotocol": "1.51.0", "vscode-nls-dev": "^3.3.1", "vscode-telemetry-extractor": "^1.9.5", "webpack": "^5.42.0", diff --git a/yarn.lock b/yarn.lock index 67d373631dfe5..3fa67b16d172c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -948,6 +948,11 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@vscode/debugprotocol@1.51.0": + version "1.51.0" + resolved "https://registry.yarnpkg.com/@vscode/debugprotocol/-/debugprotocol-1.51.0.tgz#1d28a8581f8ea74b8e2fd465d4448717589a0ae3" + integrity sha512-39ShbKzI+0r53haLZQVEhY4XhdMJVSqfcliaDFigQjqiWattno5Ex0jXq2WRHrAtPf+W5Un9/HtED0K3pAiqZg== + "@vscode/sqlite3@4.0.12": version "4.0.12" resolved "https://registry.yarnpkg.com/@vscode/sqlite3/-/sqlite3-4.0.12.tgz#50b36c788b5d130c02612b27eaf6905dc2156a43" @@ -10378,11 +10383,6 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vscode-debugprotocol@1.51.0: - version "1.51.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.51.0.tgz#c03168dac778b6c24ce17b3511cb61e89c11b2df" - integrity sha512-dzKWTMMyebIMPF1VYMuuQj7gGFq7guR8AFya0mKacu+ayptJfaRuM0mdHCqiOth4FnRP8mPhEroFPx6Ift8wHA== - vscode-nls-dev@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/vscode-nls-dev/-/vscode-nls-dev-3.3.1.tgz#15fc03e0c9ca5a150abb838690d9554ac06f77e4" From f98208be36500ef373d9f79b3fa3ad43751ea557 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 6 Jan 2022 13:29:00 -0800 Subject: [PATCH 1085/2210] Experiment of js version of hiliteColor --- .../view/renderers/backLayerWebView.ts | 8 + .../browser/view/renderers/webviewPreloads.ts | 362 ++++++++++++------ 2 files changed, 263 insertions(+), 107 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 28f1d48f66e7f..3b532b398ec79 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -356,6 +356,14 @@ export class BackLayerWebView extends Disposable { font-weight: normal; } + .find-match { + background-color: var(--vscode-editor-findMatchHighlightBackground); + } + + .current-find-match { + background-color: var(--vscode-editor-findMatchBackground); + } + #_defaultColorPalatte { color: var(--vscode-editor-findMatchHighlightBackground); background-color: var(--vscode-editor-findMatchBackground); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index e862933add921..b60e55a7ce04a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -377,6 +377,225 @@ async function webviewPreloads(ctx: PreloadContext) { return false; } + function _internalHighlightRange(range: Range, tagName = 'mark', attributes = {}) { + // derived from https://github.com/Treora/dom-highlight-range/blob/master/highlight-range.js + + // Return an array of the text nodes in the range. Split the start and end nodes if required. + function _textNodesInRange(range: Range): Text[] { + if (!range.startContainer.ownerDocument) { + return []; + } + + // If the start or end node is a text node and only partly in the range, split it. + if (range.startContainer.nodeType === Node.TEXT_NODE && range.startOffset > 0) { + const startContainer = range.startContainer as Text; + const endOffset = range.endOffset; // (this may get lost when the splitting the node) + const createdNode = startContainer.splitText(range.startOffset); + if (range.endContainer === startContainer) { + // If the end was in the same container, it will now be in the newly created node. + range.setEnd(createdNode, endOffset - range.startOffset); + } + + range.setStart(createdNode, 0); + } + + if ( + range.endContainer.nodeType === Node.TEXT_NODE + && range.endOffset < (range.endContainer as Text).length + ) { + (range.endContainer as Text).splitText(range.endOffset); + } + + // Collect the text nodes. + const walker = range.startContainer.ownerDocument.createTreeWalker( + range.commonAncestorContainer, + NodeFilter.SHOW_TEXT, + node => range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT, + ); + + walker.currentNode = range.startContainer; + + // // Optimise by skipping nodes that are explicitly outside the range. + // const NodeTypesWithCharacterOffset = [ + // Node.TEXT_NODE, + // Node.PROCESSING_INSTRUCTION_NODE, + // Node.COMMENT_NODE, + // ]; + // if (!NodeTypesWithCharacterOffset.includes(range.startContainer.nodeType)) { + // if (range.startOffset < range.startContainer.childNodes.length) { + // walker.currentNode = range.startContainer.childNodes[range.startOffset]; + // } else { + // walker.nextSibling(); // TODO verify this is correct. + // } + // } + + const nodes: Text[] = []; + if (walker.currentNode.nodeType === Node.TEXT_NODE) { + nodes.push(walker.currentNode as Text); + } + + while (walker.nextNode() && range.comparePoint(walker.currentNode, 0) !== 1) { + if (walker.currentNode.nodeType === Node.TEXT_NODE) { + nodes.push(walker.currentNode as Text); + } + } + + return nodes; + } + + // Replace [node] with [node] + function wrapNodeInHighlight(node: Text, tagName: string, attributes: any) { + const highlightElement = node.ownerDocument.createElement(tagName); + Object.keys(attributes).forEach(key => { + highlightElement.setAttribute(key, attributes[key]); + }); + const tempRange = node.ownerDocument.createRange(); + tempRange.selectNode(node); + tempRange.surroundContents(highlightElement); + return highlightElement; + } + + if (range.collapsed) { + return { + remove: () => { }, + update: () => { } + }; + } + + // First put all nodes in an array (splits start and end nodes if needed) + const nodes = _textNodesInRange(range); + + // Highlight each node + const highlightElements: Element[] = []; + for (const nodeIdx in nodes) { + const highlightElement = wrapNodeInHighlight(nodes[nodeIdx], tagName, attributes); + highlightElements.push(highlightElement); + } + + // Remove a highlight element created with wrapNodeInHighlight. + function _removeHighlight(highlightElement: Element) { + if (highlightElement.childNodes.length === 1) { + highlightElement.parentNode?.replaceChild(highlightElement.firstChild!, highlightElement); + } else { + // If the highlight somehow contains multiple nodes now, move them all. + while (highlightElement.firstChild) { + highlightElement.parentNode?.insertBefore(highlightElement.firstChild, highlightElement); + } + highlightElement.remove(); + } + } + + // Return a function that cleans up the highlightElements. + function _removeHighlights() { + // Remove each of the created highlightElements. + for (const highlightIdx in highlightElements) { + _removeHighlight(highlightElements[highlightIdx]); + } + } + + function _updateHighlight(highlightElement: Element, attributes: any = {}) { + Object.keys(attributes).forEach(key => { + highlightElement.setAttribute(key, attributes[key]); + }); + } + + function updateHighlights(attributes: any) { + for (const highlightIdx in highlightElements) { + _updateHighlight(highlightElements[highlightIdx], attributes); + } + } + + return { + remove: _removeHighlights, + update: updateHighlights + }; + } + + interface ICommonRange { + collapsed: boolean, + commonAncestorContainer: Node, + endContainer: Node, + endOffset: number, + startContainer: Node, + startOffset: number + + } + + interface IHighlightResult { + range: ICommonRange; + dispose: () => void, + update: (color: string | undefined, className: string | undefined) => void; + } + + function selectRange(_range: ICommonRange) { + const sel = window.getSelection(); + if (sel) { + try { + sel.removeAllRanges(); + const r = document.createRange(); + r.setStart(_range.startContainer, _range.startOffset); + r.setEnd(_range.endContainer, _range.endOffset); + sel.addRange(r); + } catch (e) { + console.log(e); + } + } + } + + function highlightRange(range: Range, useCustom: boolean, tagName = 'mark', attributes = {}): IHighlightResult { + if (useCustom) { + const ret = _internalHighlightRange(range, tagName, { + 'class': 'find-match' + }); + return { + range: range, + dispose: ret.remove, + update: (color: string | undefined, className: string | undefined) => { + ret.update({ + 'class': className + }); + } + }; + } else { + window.document.execCommand('hiliteColor', false, matchColor); + const cloneRange = window.getSelection()!.getRangeAt(0).cloneRange(); + const _range = { + collapsed: cloneRange.collapsed, + commonAncestorContainer: cloneRange.commonAncestorContainer, + endContainer: cloneRange.endContainer, + endOffset: cloneRange.endOffset, + startContainer: cloneRange.startContainer, + startOffset: cloneRange.startOffset + }; + return { + range: _range, + dispose: () => { + selectRange(_range); + try { + document.designMode = 'On'; + document.execCommand('removeFormat', false, undefined); + document.designMode = 'Off'; + window.getSelection()?.removeAllRanges(); + } catch (e) { + console.log(e); + } + }, + update: (color: string | undefined, className: string | undefined) => { + selectRange(_range); + try { + document.designMode = 'On'; + document.execCommand('removeFormat', false, undefined); + window.document.execCommand('hiliteColor', false, color); + document.designMode = 'Off'; + window.getSelection()?.removeAllRanges(); + } catch (e) { + console.log(e); + } + } + }; + } + } + class OutputFocusTracker { private _outputId: string; private _hasFocus: boolean = false; @@ -533,20 +752,29 @@ async function webviewPreloads(ctx: PreloadContext) { window.addEventListener('wheel', handleWheel); - let _findingMatches: { - type: any; - cellId: string; - id: string; - container: any; - range: any; - }[] = []; + interface IFindMatch { + type: 'input' | 'output', + id: string, + cellId: string, + container: Node, + highlightResult: IHighlightResult + } + + let _findingMatches: IFindMatch[] = []; let _findMatchIndex = -1; let matchColor = window.getComputedStyle(document.getElementById('_defaultColorPalatte')!).color; let currentMatchColor = window.getComputedStyle(document.getElementById('_defaultColorPalatte')!).backgroundColor; const find = (query: string) => { let find = true; - let matches = []; + let matches: { + type: 'input' | 'output', + id: string, + cellId: string, + container: Node, + originalRange: Range, + highlightResult?: IHighlightResult + }[] = []; let range = document.createRange(); range.selectNodeContents(document.getElementById('findStart')!); @@ -576,23 +804,12 @@ async function webviewPreloads(ctx: PreloadContext) { const lastEl: any = matches.length ? matches[matches.length - 1] : null; if (lastEl && lastEl.container.contains(anchorNode)) { - // document.execCommand('hiliteColor', false, '#ff0000'); - window.document.execCommand('hiliteColor', false, matchColor); - - const range = window.getSelection()!.getRangeAt(0).cloneRange(); matches.push({ type: lastEl.type, id: lastEl.id, cellId: lastEl.cellId, container: lastEl.container, - range: { - collapsed: range.collapsed, - commonAncestorContainer: range.commonAncestorContainer, - endContainer: range.endContainer, - endOffset: range.endOffset, - startContainer: range.startContainer, - startOffset: range.startOffset - } + originalRange: window.getSelection()!.getRangeAt(0) }); } else { @@ -605,24 +822,12 @@ async function webviewPreloads(ctx: PreloadContext) { // inside output const cellId = node.parentElement?.parentElement?.id; if (cellId) { - // document.execCommand('hiliteColor', false, '#ff0000'); - window.document.execCommand('hiliteColor', false, matchColor); - - const range = window.getSelection()!.getRangeAt(0); - matches.push({ type: 'output', id: node.id, cellId: cellId, container: node, - range: { - collapsed: range.collapsed, - commonAncestorContainer: range.commonAncestorContainer, - endContainer: range.endContainer, - endOffset: range.endOffset, - startContainer: range.startContainer, - startOffset: range.startOffset - } + originalRange: window.getSelection()!.getRangeAt(0) }); } @@ -641,9 +846,15 @@ async function webviewPreloads(ctx: PreloadContext) { } } + for (let i = matches.length - 1; i >= 0; i--) { + const match = matches[i]; + const ret = highlightRange(match.originalRange, true); + match.highlightResult = ret; + } + document.designMode = 'Off'; - _findingMatches = matches; + _findingMatches = matches as IFindMatch[]; postNotebookMessage('didFind', { matches: matches.map((match, index) => ({ type: match.type, @@ -657,106 +868,43 @@ async function webviewPreloads(ctx: PreloadContext) { const highlightCurrentMatch = (index: number) => { const oldMatch = _findingMatches[_findMatchIndex]; if (oldMatch) { - const sel = window.getSelection(); - if (sel) { - try { - sel.removeAllRanges(); - const r = document.createRange(); - r.setStart(oldMatch.range.startContainer, oldMatch.range.startOffset); - r.setEnd(oldMatch.range.endContainer, oldMatch.range.endOffset); - sel.addRange(r); - document.designMode = 'On'; - document.execCommand('removeFormat', false, undefined); - window.document.execCommand('hiliteColor', false, matchColor); - document.designMode = 'Off'; - - sel.removeAllRanges(); - } catch (e) { - console.log(e); - } - } + oldMatch.highlightResult.update(matchColor, 'find-match'); } + const match = _findingMatches[index]; _findMatchIndex = index; const sel = window.getSelection(); if (!!match && !!sel) { - // const outputOffset = match.cellId let offset = 0; try { const outputOffset = document.getElementById(match.id)!.getBoundingClientRect().top; const tempRange = document.createRange(); - tempRange.selectNode(match.range.startContainer); + tempRange.selectNode(match.highlightResult.range.startContainer); const rangeOffset = tempRange.getBoundingClientRect().top; tempRange.detach(); offset = rangeOffset - outputOffset; } catch (e) { } - try { - sel.removeAllRanges(); - const r = document.createRange(); - r.setStart(match.range.startContainer, match.range.startOffset); - r.setEnd(match.range.endContainer, match.range.endOffset); - sel.addRange(r); - document.designMode = 'On'; - window.document.execCommand('hiliteColor', false, currentMatchColor); - document.designMode = 'Off'; + match.highlightResult.update(currentMatchColor, 'current-find-match'); - sel.removeAllRanges(); - - postNotebookMessage('didFindHighlight', { - offset - }); - } catch (e) { - console.log(e); - } + document.getSelection()?.removeAllRanges(); + postNotebookMessage('didFindHighlight', { + offset + }); } }; const unHighlightCurrentMatch = (index: number) => { const oldMatch = _findingMatches[index]; if (oldMatch) { - const sel = window.getSelection(); - if (sel) { - try { - sel.removeAllRanges(); - const r = document.createRange(); - r.setStart(oldMatch.range.startContainer, oldMatch.range.startOffset); - r.setEnd(oldMatch.range.endContainer, oldMatch.range.endOffset); - sel.addRange(r); - document.designMode = 'On'; - document.execCommand('removeFormat', false, undefined); - window.document.execCommand('hiliteColor', false, matchColor); - document.designMode = 'Off'; - - sel.removeAllRanges(); - } catch (e) { - console.log(e); - } - } + oldMatch.highlightResult.update(matchColor, 'find-match'); } }; const clearFindMatches = () => { _findingMatches.forEach(match => { - const sel = window.getSelection(); - if (sel) { - try { - sel.removeAllRanges(); - const r = document.createRange(); - r.setStart(match.range.startContainer, match.range.startOffset); - r.setEnd(match.range.endContainer, match.range.endOffset); - sel.addRange(r); - document.designMode = 'On'; - // document.execCommand('hiliteColor', false, '#ff0000'); - document.execCommand('removeFormat', false, undefined); - document.designMode = 'Off'; - - sel.removeAllRanges(); - } catch (e) { - console.log(e); - } - } + match.highlightResult.dispose(); }); _findingMatches = []; From eb7166d4af4ac857f1ba14d1762ff5d98b64ad5d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 6 Jan 2022 14:45:14 -0800 Subject: [PATCH 1086/2210] fixes #140189 remove debug line --- src/vs/workbench/browser/layout.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 6be4016512a52..b627088d5bbae 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1265,7 +1265,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } centerEditorLayout(active: boolean, skipLayout?: boolean): void { - active = false; this.stateModel.setRuntimeValue(LayoutStateKeys.EDITOR_CENTERED, active); let smartActive = active; From 732ee52fea57a83533a757200cd5fe933b81a34f Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 6 Jan 2022 15:23:53 -0800 Subject: [PATCH 1087/2210] shadow dom support. --- .../browser/view/renderers/webviewPreloads.ts | 70 ++++++++++++++++--- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index b60e55a7ce04a..5d9ae0b79b8a4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -544,16 +544,20 @@ async function webviewPreloads(ctx: PreloadContext) { function highlightRange(range: Range, useCustom: boolean, tagName = 'mark', attributes = {}): IHighlightResult { if (useCustom) { - const ret = _internalHighlightRange(range, tagName, { - 'class': 'find-match' - }); + const ret = _internalHighlightRange(range, tagName, attributes); return { range: range, dispose: ret.remove, update: (color: string | undefined, className: string | undefined) => { - ret.update({ - 'class': className - }); + if (className === undefined) { + ret.update({ + 'style': `background-color: ${color}` + }); + } else { + ret.update({ + 'class': className + }); + } } }; } else { @@ -757,6 +761,7 @@ async function webviewPreloads(ctx: PreloadContext) { id: string, cellId: string, container: Node, + isShadow: boolean, highlightResult: IHighlightResult } @@ -773,6 +778,7 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: string, container: Node, originalRange: Range, + isShadow: boolean, highlightResult?: IHighlightResult }[] = []; @@ -798,6 +804,43 @@ async function webviewPreloads(ctx: PreloadContext) { break; } + if (selection.getRangeAt(0).startContainer.nodeType === 1 + && (selection.getRangeAt(0).startContainer as Element).classList.contains('widgetarea')) { + // markdown preview container' + const preview = (selection.anchorNode?.firstChild as Element); + const root = preview.shadowRoot as ShadowRoot & { getSelection: () => Selection }; + const shadowSelection = root?.getSelection ? root?.getSelection() : null; + if (shadowSelection && shadowSelection.anchorNode) { + matches.push({ + type: 'input', + id: preview.id, + cellId: preview.id, + container: preview, + isShadow: true, + originalRange: shadowSelection.getRangeAt(0) + }); + } + } + + if (selection.getRangeAt(0).startContainer.nodeType === 1 + && (selection.getRangeAt(0).startContainer as Element).classList.contains('output_container')) { + // output container + const cellId = selection.getRangeAt(0).startContainer.parentElement!.id; + const outputNode = (selection.anchorNode?.firstChild as Element); + const root = outputNode.shadowRoot as ShadowRoot & { getSelection: () => Selection }; + const shadowSelection = root?.getSelection ? root?.getSelection() : null; + if (shadowSelection && shadowSelection.anchorNode) { + matches.push({ + type: 'output', + id: outputNode.id, + cellId: cellId, + container: outputNode, + isShadow: true, + originalRange: shadowSelection.getRangeAt(0) + }); + } + } + const anchorNode = selection?.anchorNode?.parentElement; if (anchorNode) { @@ -809,6 +852,7 @@ async function webviewPreloads(ctx: PreloadContext) { id: lastEl.id, cellId: lastEl.cellId, container: lastEl.container, + isShadow: false, originalRange: window.getSelection()!.getRangeAt(0) }); @@ -827,9 +871,9 @@ async function webviewPreloads(ctx: PreloadContext) { id: node.id, cellId: cellId, container: node, + isShadow: false, originalRange: window.getSelection()!.getRangeAt(0) }); - } break; } @@ -848,7 +892,11 @@ async function webviewPreloads(ctx: PreloadContext) { for (let i = matches.length - 1; i >= 0; i--) { const match = matches[i]; - const ret = highlightRange(match.originalRange, true); + const ret = highlightRange(match.originalRange, true, 'mark', match.isShadow ? { + 'style': 'background-color: ' + matchColor + ';', + } : { + 'class': 'find-match' + }); match.highlightResult = ret; } @@ -868,7 +916,7 @@ async function webviewPreloads(ctx: PreloadContext) { const highlightCurrentMatch = (index: number) => { const oldMatch = _findingMatches[_findMatchIndex]; if (oldMatch) { - oldMatch.highlightResult.update(matchColor, 'find-match'); + oldMatch.highlightResult.update(matchColor, oldMatch.isShadow ? undefined : 'find-match'); } const match = _findingMatches[index]; @@ -886,7 +934,7 @@ async function webviewPreloads(ctx: PreloadContext) { } catch (e) { } - match.highlightResult.update(currentMatchColor, 'current-find-match'); + match.highlightResult.update(currentMatchColor, match.isShadow ? undefined : 'current-find-match'); document.getSelection()?.removeAllRanges(); postNotebookMessage('didFindHighlight', { @@ -898,7 +946,7 @@ async function webviewPreloads(ctx: PreloadContext) { const unHighlightCurrentMatch = (index: number) => { const oldMatch = _findingMatches[index]; if (oldMatch) { - oldMatch.highlightResult.update(matchColor, 'find-match'); + oldMatch.highlightResult.update(matchColor, oldMatch.isShadow ? undefined : 'find-match'); } }; From f5bdf0557a219043d5f749f9bdd8e71b637304b4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 6 Jan 2022 15:35:58 -0800 Subject: [PATCH 1088/2210] Pull shell integration browser-side logic into an addon --- src/vs/platform/terminal/common/terminal.ts | 5 +- .../terminal/browser/terminalInstance.ts | 5 - .../browser/xterm/shellIntegrationAddon.ts | 128 ++++++++++++++++++ .../terminal/browser/xterm/xtermTerminal.ts | 79 ++++------- .../contrib/terminal/common/terminal.ts | 9 +- 5 files changed, 164 insertions(+), 62 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 887c7820d785e..8a73f1fe86864 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -379,11 +379,10 @@ export interface TerminalCommand { exitCode?: number; } -export enum ShellIntegrationInfo { +export const enum ShellIntegrationInfo { CurrentDir = 'CurrentDir', } - -export enum ShellIntegrationInteraction { +export const enum ShellIntegrationInteraction { PromptStart = 'PROMPT_START', CommandStart = 'COMMAND_START', CommandExecuted = 'COMMAND_EXECUTED', diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index a3c6ca278fc0f..2111cf27ab678 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -68,7 +68,6 @@ import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnviro import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { isFirefox } from 'vs/base/browser/browser'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; -import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { fromNow } from 'vs/base/common/date'; const enum Constants { @@ -1211,10 +1210,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { case ProcessPropertyType.HasChildProcesses: this._onDidChangeHasChildProcesses.fire(value); break; - case ProcessPropertyType.Capability: - if (value === ProcessCapability.CommandCognisant && !(this.xterm?.commandTracker instanceof CognisantCommandTrackerAddon)) { - this.xterm?.upgradeCommandTracker(); - } } }); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts new file mode 100644 index 0000000000000..2cc39b2346ad0 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITerminalAddon, Terminal } from 'xterm'; +import { IShellIntegration } from 'vs/workbench/contrib/terminal/common/terminal'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ProcessCapability, ShellIntegrationInfo, ShellIntegrationInteraction } from 'vs/platform/terminal/common/terminal'; + +/** + * Shell integration is a feature that enhances the terminal's understanding of what's happening + * in the shell by injecting special sequences into the shell's prompt using the "Set Text + * Parameters" sequence (`OSC Ps ; Pt ST`). + * + * Definitions: + * - OSC: `\x1b]` + * - Ps: A single (usually optional) numeric parameter, composed of one or more digits. + * - Pt: A text parameter composed of printable characters. + * - ST: `\x7` + * + * This is inspired by a feature of the same name in the FinalTerm, iTerm2 and kitty terminals. + */ + +/** + * The identifier for the first numeric parameter (`Ps`) for OSC commands used by shell integration. + */ +const enum ShellIntegrationOscPs { + /** + * Sequences pioneered by FinalTerm. + */ + FinalTerm = 133, + /** + * Sequences pioneered by iTerm. + */ + ITerm = 1337 +} + +/** + * The identifier for the textural parameter (`Pt`) for OSC commands used by shell integration. + */ +const enum ShellIntegrationOscPt { + /** + * The start of the prompt, this is expected to always appear at the start of a line. + */ + PromptStart = 'A', + /** + * The start of a command, ie. where the user inputs their command. + */ + CommandStart = 'B', + /** + * Sent just before the command output begins. + */ + CommandExecuted = 'C', + // TODO: Understand this sequence better and add docs + CommandFinished = 'D', + // TODO: This is a VS Code-specific sequence? Do we need this? Should it have a version? + EnableShellIntegration = 'E', +} + +export class ShellIntegrationAddon extends Disposable implements IShellIntegration, ITerminalAddon { + private _terminal?: Terminal; + + // TODO: Rename ProcessCapability to TerminalCapability, move naive CwdDetection to renderer + private readonly _onCapabilityDisabled = new Emitter(); + readonly onCapabilityDisabled = this._onCapabilityDisabled.event; + private readonly _onCapabilityEnabled = new Emitter(); + readonly onCapabilityEnabled = this._onCapabilityEnabled.event; + private readonly _onIntegratedShellChange = new Emitter<{ type: string, value: string }>(); + readonly onIntegratedShellChange = this._onIntegratedShellChange.event; + + activate(xterm: Terminal) { + this._terminal = xterm; + this._register(xterm.parser.registerOscHandler(ShellIntegrationOscPs.FinalTerm, data => this._handleShellIntegration(data))); + this._register(xterm.parser.registerOscHandler(ShellIntegrationOscPs.ITerm, data => this._updateCwd(data))); + } + + private _handleShellIntegration(data: string): boolean { + if (!this._terminal) { + return false; + } + let type: ShellIntegrationInteraction | undefined; + const [command, exitCode] = data.split(';'); + switch (command) { + case ShellIntegrationOscPt.PromptStart: + type = ShellIntegrationInteraction.PromptStart; + break; + case ShellIntegrationOscPt.CommandStart: + type = ShellIntegrationInteraction.CommandStart; + break; + case ShellIntegrationOscPt.CommandExecuted: + type = ShellIntegrationInteraction.CommandExecuted; + break; + case ShellIntegrationOscPt.CommandFinished: + type = ShellIntegrationInteraction.CommandFinished; + break; + case ShellIntegrationOscPt.EnableShellIntegration: + this._onCapabilityEnabled.fire(ProcessCapability.CommandCognisant); + return true; + default: + return false; + } + const value = exitCode || type; + if (!value) { + return false; + } + this._onIntegratedShellChange.fire({ type, value }); + return true; + } + + private _updateCwd(data: string): boolean { + let value: string | undefined; + const [type, info] = data.split('='); + switch (type) { + case ShellIntegrationInfo.CurrentDir: + value = info; + break; + default: + return false; + } + if (!value) { + return false; + } + this._onIntegratedShellChange.fire({ type, value }); + return true; + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index aed3a0621595c..68a712373ad0c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -12,7 +12,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { ProcessCapability, ShellIntegrationInfo, ShellIntegrationInteraction, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ProcessCapability, ShellIntegrationInteraction, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ICommandTracker, ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -28,6 +28,7 @@ import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TERMINAL_FOREGROUND_COLOR, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR, ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { Color } from 'vs/base/common/color'; +import { ShellIntegrationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -55,6 +56,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Optional addons private _searchAddon?: SearchAddonType; + private _shellIntegrationAddon?: ShellIntegrationAddon; private _unicode11Addon?: Unicode11AddonType; private _webglAddon?: WebglAddonType; @@ -119,8 +121,6 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { })); this._core = (this.raw as any)._core as IXtermCore; - this.raw.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); - this.raw.parser.registerOscHandler(1337, (data => this._updateCwd(data))); this.add(this._configurationService.onDidChangeConfiguration(async e => { if (e.affectsConfiguration(TerminalSettingId.GpuAcceleration)) { XtermTerminal._suggestedRendererType = undefined; @@ -144,64 +144,37 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._updateUnicodeVersion(); this._commandTrackerAddon = new NaiveCommandTrackerAddon(); this.raw.loadAddon(this._commandTrackerAddon); + this._shellIntegrationAddon = new ShellIntegrationAddon(); + this.raw.loadAddon(this._shellIntegrationAddon); + + // Hook up co-dependent addon events + this._shellIntegrationAddon.onCapabilityEnabled(e => { + if (e === ProcessCapability.CommandCognisant) { + this.upgradeCommandTracker(); + } + }); + this._shellIntegrationAddon.onIntegratedShellChange(e => { + if (e.type === ShellIntegrationInteraction.CommandFinished) { + // TODO: This shoudl move into the new command tracker + if (this.raw.buffer.active.cursorX >= 2) { + this.raw.registerMarker(0); + this.commandTracker.clearMarker(); + } + } + this._commandTrackerAddon.handleIntegratedShellChange(e); + }); + } upgradeCommandTracker(): void { - this.raw.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); - this.raw.parser.registerOscHandler(1337, (data => this._updateCwd(data))); + if (this._commandTrackerAddon instanceof CognisantCommandTrackerAddon) { + return; + } this._commandTrackerAddon.dispose(); this._commandTrackerAddon = new CognisantCommandTrackerAddon(); this._commandTrackerAddon.activate(this.raw); } - private _updateCwd(data: string): boolean { - let value: string | undefined; - const [type, info] = data.split('='); - switch (type) { - case ShellIntegrationInfo.CurrentDir: - value = info; - break; - default: - return false; - } - if (!value) { - return false; - } - this._commandTrackerAddon.handleIntegratedShellChange({ type, value }); - return true; - } - - private _handleShellIntegration(data: string): boolean { - let type: ShellIntegrationInteraction | undefined; - const [command, exitCode] = data.split(';'); - switch (command) { - case 'A': - type = ShellIntegrationInteraction.PromptStart; - break; - case 'B': - type = ShellIntegrationInteraction.CommandStart; - break; - case 'C': - type = ShellIntegrationInteraction.CommandExecuted; - break; - case 'D': - type = ShellIntegrationInteraction.CommandFinished; - if (this.raw.buffer.active.cursorX >= 2) { - this.raw?.registerMarker(0); - this.commandTracker.clearMarker(); - } - break; - default: - return false; - } - const value = exitCode || type; - if (!value) { - return false; - } - this._commandTrackerAddon.handleIntegratedShellChange({ type, value }); - return true; - } - attachToElement(container: HTMLElement) { // Update the theme when attaching as the terminal location could have changed this._updateTheme(); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 83f99c2d1d7e8..9be3f9c7c112e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, TerminalCommand } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, TerminalCommand, ProcessCapability } from 'vs/platform/terminal/common/terminal'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; @@ -330,6 +330,13 @@ export interface ICommandTracker { clearMarker(): void; } +export interface IShellIntegration { + readonly onCapabilityEnabled: Event; + readonly onCapabilityDisabled: Event; + // TODO: Fire more fine-grained and stronger typed events + readonly onIntegratedShellChange: Event<{ type: string, value: string }>; +} + export interface INavigationMode { exitNavigationMode(): void; focusPreviousLine(): void; From 1bcb0af4868950e39bcf833abeabf3186ffcf134 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Wed, 5 Jan 2022 16:12:07 -0800 Subject: [PATCH 1089/2210] Add field for split editor in default settings Adds a new content field for the default settings without the most commonly used group. The field is then consumed by the default setting side of the split json editor. Fixes #137122 --- .../preferences/browser/preferencesService.ts | 7 ++++--- .../preferences/common/preferencesModels.ts | 20 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 6c2972c6d3470..0f72ad2383d0c 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -120,7 +120,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic resolveModel(uri: URI): ITextModel | null { if (this.isDefaultSettingsResource(uri)) { - + // We opened a split json editor in this case, + // and this half shows the default settings. const target = this.getConfigurationTargetFromDefaultSettingsResource(uri); const languageSelection = this.languageService.createById('jsonc'); const model = this._register(this.modelService.createModel('', languageSelection, uri)); @@ -134,7 +135,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return; } defaultSettings = this.getDefaultSettings(target); - this.modelService.updateModel(model, defaultSettings.getContent(true)); + this.modelService.updateModel(model, defaultSettings.getContentWithoutMostCommonlyUsed(true)); defaultSettings._onDidChange.fire(); } }); @@ -142,7 +143,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic // Check if Default settings is already created and updated in above promise if (!defaultSettings) { defaultSettings = this.getDefaultSettings(target); - this.modelService.updateModel(model, defaultSettings.getContent(true)); + this.modelService.updateModel(model, defaultSettings.getContentWithoutMostCommonlyUsed(true)); } return model; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 139be78f14527..144dfee36d717 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -461,6 +461,7 @@ export class DefaultSettings extends Disposable { private _allSettingsGroups: ISettingsGroup[] | undefined; private _content: string | undefined; + private _contentWithoutMostCommonlyUsed: string | undefined; private _settingsByName = new Map(); readonly _onDidChange: Emitter = this._register(new Emitter()); @@ -481,6 +482,14 @@ export class DefaultSettings extends Disposable { return this._content!; } + getContentWithoutMostCommonlyUsed(forceUpdate = false): string { + if (!this._contentWithoutMostCommonlyUsed || forceUpdate) { + this.initialize(); + } + + return this._contentWithoutMostCommonlyUsed!; + } + getSettingsGroups(forceUpdate = false): ISettingsGroup[] { if (!this._allSettingsGroups || forceUpdate) { this.initialize(); @@ -491,7 +500,8 @@ export class DefaultSettings extends Disposable { private initialize(): void { this._allSettingsGroups = this.parse(); - this._content = this.toContent(this._allSettingsGroups); + this._content = this.toContent(this._allSettingsGroups, 0); + this._contentWithoutMostCommonlyUsed = this.toContent(this._allSettingsGroups, 1); } private parse(): ISettingsGroup[] { @@ -738,11 +748,11 @@ export class DefaultSettings extends Disposable { return c1.order - c2.order; } - private toContent(settingsGroups: ISettingsGroup[]): string { + private toContent(settingsGroups: ISettingsGroup[], startIndex: number): string { const builder = new SettingsContentBuilder(); - settingsGroups.forEach((settingsGroup, i) => { - builder.pushGroup(settingsGroup, i === 0, i === settingsGroups.length - 1); - }); + for (let i = startIndex; i < settingsGroups.length; i++) { + builder.pushGroup(settingsGroups[i], i === startIndex, i === settingsGroups.length - 1); + } return builder.getContent(); } From b3dcf63c3dff2a430300e0d9faa910f883224f04 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 5 Jan 2022 18:02:02 -0800 Subject: [PATCH 1090/2210] Return 401 for unathorized resources This makes it clearer why a request is failing --- .../contrib/webview/browser/pre/service-worker.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js index 5b499cf186a19..01b1536f88607 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js @@ -118,6 +118,9 @@ const resourceRequestStore = new RequestStore(); */ const localhostRequestStore = new RequestStore(); +const unauthorized = () => + new Response('Unauthorized', { status: 401, }); + const notFound = () => new Response('Not Found', { status: 404, }); @@ -216,6 +219,10 @@ async function processResourceRequest(event, requestUrl) { } } + if (entry.status === 401) { + return unauthorized(); + } + if (entry.status !== 200) { return notFound(); } From 4617e0c73bf1b0fdbef681289eb92ea1067ec299 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 6 Jan 2022 15:35:13 -0800 Subject: [PATCH 1091/2210] Adding more logging for webview resource loading --- .../browser/view/renderers/webviewPreloads.ts | 4 ++++ src/vs/workbench/contrib/webview/browser/pre/main.js | 1 + .../contrib/webview/browser/pre/service-worker.js | 11 ++++++----- .../contrib/webview/browser/webviewElement.ts | 7 +++++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 3f0ef6c528711..5b94dff58a640 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -207,6 +207,10 @@ async function webviewPreloads(ctx: PreloadContext) { try { if (isModule) { const module: KernelPreloadModule = await __import(url); + if (!module.activate) { + console.error(`Notebook preload (${url}) looks like a module but does not export an activate function`); + return; + } return module.activate(createKernelContext()); } else { return invokeSourceWithGlobals(text, { ...kernelPreloadGlobals, scriptUrl: url }); diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index fb63fa1dcb95a..f318332bf7f71 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -214,6 +214,7 @@ const workerReady = new Promise((resolve, reject) => { */ const versionHandler = async (event) => { if (event.data.channel !== 'init') { + console.log('Unknown message received in webview from service worker'); return; } navigator.serviceWorker.removeEventListener('message', versionHandler); diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js index 01b1536f88607..976f190336b45 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js @@ -142,16 +142,16 @@ sw.addEventListener('message', event => { }); return; } + default: + console.log('Unknown message'); + return; } - - console.log('Unknown message'); }); vscodeMessageChannel.port1.onmessage = (event) => { switch (event.data.channel) { case 'did-load-resource': { - /** @type {ResourceResponse} */ const response = event.data; if (!resourceRequestStore.resolve(response.id, response)) { @@ -167,9 +167,10 @@ vscodeMessageChannel.port1.onmessage = (event) => { } return; } + default: + console.log('Unknown message'); + return; } - - console.log('Unknown message'); }; sw.addEventListener('fetch', (event) => { diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index b60187c87f1c8..ec14312a1ae7b 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -217,6 +217,10 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this.messagePort = e.ports[0]; this.messagePort.onmessage = (e) => { const handlers = this._messageHandlers.get(e.data.channel); + if (!handlers) { + console.log(`No handlers found for '${e.data.channel}'`); + return; + } handlers?.forEach(handler => handler(e.data.data, e)); }; @@ -364,6 +368,9 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD }); } } + default: + console.log('Unknown message received in renderer process from service worker port'); + return; } }; From 1666e1d9cc6b401ccf603a581160441ad6a58839 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 6 Jan 2022 16:21:39 -0800 Subject: [PATCH 1092/2210] Move sw message registration before call to register This should make sure we get any messages from the sw --- .../contrib/webview/browser/pre/main.js | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index f318332bf7f71..244bd1d4af20b 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -206,27 +206,27 @@ const workerReady = new Promise((resolve, reject) => { const swPath = `service-worker.js?v=${expectedWorkerVersion}&vscode-resource-base-authority=${searchParams.get('vscode-resource-base-authority')}`; - navigator.serviceWorker.register(swPath).then( - async registration => { - await navigator.serviceWorker.ready; - /** - * @param {MessageEvent} event - */ - const versionHandler = async (event) => { - if (event.data.channel !== 'init') { - console.log('Unknown message received in webview from service worker'); - return; - } - navigator.serviceWorker.removeEventListener('message', versionHandler); + /** + * @param {MessageEvent} event + */ + const swMessageHandler = async (event) => { + if (event.data.channel !== 'init') { + console.log('Unknown message received in webview from service worker'); + return; + } + navigator.serviceWorker.removeEventListener('message', swMessageHandler); - // Forward the port back to VS Code - hostMessaging.onMessage('did-init-service-worker', () => resolve()); - hostMessaging.postMessage('init-service-worker', {}, event.ports); - }; - navigator.serviceWorker.addEventListener('message', versionHandler); + // Forward the port back to VS Code + hostMessaging.onMessage('did-init-service-worker', () => resolve()); + hostMessaging.postMessage('init-service-worker', {}, event.ports); + }; + navigator.serviceWorker.addEventListener('message', swMessageHandler); - const postVersionMessage = () => { - assertIsDefined(navigator.serviceWorker.controller).postMessage({ channel: 'init' }); + navigator.serviceWorker.register(swPath) + .then(() => navigator.serviceWorker.ready) + .then(() => { + const initServiceWorker = (/** @type {ServiceWorker} */ worker) => { + worker.postMessage({ channel: 'init' }); }; // At this point, either the service worker is ready and @@ -234,20 +234,19 @@ const workerReady = new Promise((resolve, reject) => { // Note that navigator.serviceWorker.controller could be a // controller from a previously loaded service worker. const currentController = navigator.serviceWorker.controller; - if (currentController && currentController.scriptURL.endsWith(swPath)) { + if (currentController?.scriptURL.endsWith(swPath)) { // service worker already loaded & ready to receive messages - postVersionMessage(); + initServiceWorker(currentController); } else { // either there's no controlling service worker, or it's an old one: // wait for it to change before posting the message const onControllerChange = () => { navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange); - postVersionMessage(); + initServiceWorker(navigator.serviceWorker.controller); }; navigator.serviceWorker.addEventListener('controllerchange', onControllerChange); } - }, - error => { + }).catch(error => { reject(new Error(`Could not register service workers: ${error}.`)); }); }); From ba06d4d023290c5f6eda35c0498d5b33d22f092a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 6 Jan 2022 16:25:59 -0800 Subject: [PATCH 1093/2210] Remove unused messages --- src/vs/workbench/contrib/webview/browser/pre/main.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 244bd1d4af20b..00f7410b30477 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -893,8 +893,6 @@ onDomReady(() => { }); pendingMessages = []; } - - hostMessaging.postMessage('did-load'); }; /** @@ -941,8 +939,6 @@ onDomReady(() => { unloadMonitor.onIframeLoaded(newFrame); } - - hostMessaging.postMessage('did-set-content', undefined); }); // Forward message to the embedded iframe From 6c8a870cbe34aa50b727450b43a8e9461990972e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 18:27:50 -0600 Subject: [PATCH 1094/2210] remove eslint override --- .../contrib/terminal/browser/xterm/commandTrackerAddon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index feface491761c..7dfb6ae4e26ab 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -371,12 +371,11 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { break; case ShellIntegrationInteraction.CommandExecuted: break; - case ShellIntegrationInteraction.CommandFinished: + case ShellIntegrationInteraction.CommandFinished: { this._exitCode = Number.parseInt(event.value); if (!this._commandMarker?.line || !this._terminal?.buffer.active) { break; } - // eslint-disable-next-line no-case-declarations const command = this._terminal.buffer.active.getLine(this._commandMarker.line)?.translateToString().substring(this._commandCharStart || 0); if (command && !command.startsWith('\\') && command !== '') { this._commands.push( @@ -388,6 +387,7 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { }); } break; + } default: return; } From f16b9b552c9e13b93dade513b2019e94974d64d7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 6 Jan 2022 18:28:29 -0600 Subject: [PATCH 1095/2210] if not terminal, return --- .../terminal/browser/xterm/commandTrackerAddon.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index 7dfb6ae4e26ab..d16f50405d6c6 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -358,6 +358,9 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { } handleIntegratedShellChange(event: { type: string, value: string }): void { + if (!this._terminal) { + return; + } switch (event.type) { case ShellIntegrationInfo.CurrentDir: this._cwd = event.value; @@ -366,14 +369,14 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { case ShellIntegrationInteraction.PromptStart: break; case ShellIntegrationInteraction.CommandStart: - this._commandMarker = this._terminal?.registerMarker(0); - this._commandCharStart = this._terminal?.buffer.active.cursorX; + this._commandMarker = this._terminal.registerMarker(0); + this._commandCharStart = this._terminal.buffer.active.cursorX; break; case ShellIntegrationInteraction.CommandExecuted: break; case ShellIntegrationInteraction.CommandFinished: { this._exitCode = Number.parseInt(event.value); - if (!this._commandMarker?.line || !this._terminal?.buffer.active) { + if (!this._commandMarker?.line || !this._terminal.buffer.active) { break; } const command = this._terminal.buffer.active.getLine(this._commandMarker.line)?.translateToString().substring(this._commandCharStart || 0); From 915192507c2fb6302f7ba159fd66d1c91962fa5b Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 6 Jan 2022 17:57:03 -0800 Subject: [PATCH 1096/2210] bump find match limit --- .../notebook/browser/view/renderers/webviewPreloads.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 5d9ae0b79b8a4..505a355f12de3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -789,7 +789,7 @@ async function webviewPreloads(ctx: PreloadContext) { sel?.addRange(range); document.designMode = 'On'; - while (find && matches.length < 200) { + while (find && matches.length < 500) { find = (window as any).find(query, /* caseSensitive*/ false, /* backwards*/ false, /* wrapAround*/ false, @@ -806,7 +806,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (selection.getRangeAt(0).startContainer.nodeType === 1 && (selection.getRangeAt(0).startContainer as Element).classList.contains('widgetarea')) { - // markdown preview container' + // markdown preview container const preview = (selection.anchorNode?.firstChild as Element); const root = preview.shadowRoot as ShadowRoot & { getSelection: () => Selection }; const shadowSelection = root?.getSelection ? root?.getSelection() : null; From cc601f4784c14420622c5987b6f09a2b75f0da4c Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 6 Jan 2022 17:59:44 -0800 Subject: [PATCH 1097/2210] clear selection at the end of search. --- .../contrib/notebook/browser/view/renderers/webviewPreloads.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 505a355f12de3..07cf35128c44d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -901,6 +901,7 @@ async function webviewPreloads(ctx: PreloadContext) { } document.designMode = 'Off'; + document.getSelection()?.collapseToStart(); _findingMatches = matches as IFindMatch[]; postNotebookMessage('didFind', { From e3a847db98f4dc92bfa8f00068e84911ac29a78c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 6 Jan 2022 19:23:47 -0800 Subject: [PATCH 1098/2210] Remove isPaused from internal cell metadata --- .../breakpoints/notebookBreakpoints.ts | 20 ++++--------------- .../executionStatusBarItemController.ts | 10 +++++----- .../notebookExecutionStateServiceImpl.ts | 1 - .../browser/view/cellParts/cellProgressBar.ts | 2 +- .../contrib/notebook/common/notebookCommon.ts | 1 - 5 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts b/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts index 568dca1aad126..f1836653a29c9 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts @@ -15,7 +15,7 @@ import { IBreakpoint, IDebugService } from 'vs/workbench/contrib/debug/common/de import { Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellEditType, CellUri, NotebookCellsChangeType, NullablePartialNotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -135,7 +135,6 @@ class NotebookCellPausing extends Disposable implements IWorkbenchContribution { constructor( @IDebugService private readonly _debugService: IDebugService, - @INotebookService private readonly _notebookService: INotebookService, @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, ) { super(); @@ -175,25 +174,14 @@ class NotebookCellPausing extends Disposable implements IWorkbenchContribution { private editIsPaused(cellUri: URI, isPaused: boolean) { const parsed = CellUri.parse(cellUri); - if (parsed) { - const notebookModel = this._notebookService.getNotebookTextModel(parsed.notebook); - const internalMetadata: NullablePartialNotebookCellInternalMetadata = { - isPaused - }; - if (isPaused) { + if (parsed && isPaused) { + const exeState = this._notebookExecutionStateService.getCellExecutionState(cellUri); + if (exeState) { this._notebookExecutionStateService.updateNotebookCellExecution(parsed.notebook, parsed.handle, [{ editType: CellExecutionUpdateType.ExecutionState, didPause: true }]); } - - if (notebookModel?.checkCellExistence(parsed.handle)) { - notebookModel?.applyEdits([{ - editType: CellEditType.PartialInternalMetadata, - handle: parsed.handle, - internalMetadata, - }], true, undefined, () => undefined, undefined); - } } } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts index a57604e0b69d5..83360870d7387 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts @@ -111,11 +111,11 @@ class ExecutionStateCellStatusBarItem extends Disposable { * Returns undefined if there should be no change, and an empty array if all items should be removed. */ private _getItemsForCell(): INotebookCellStatusBarItem[] | undefined { - if (this._currentExecutingStateTimer && !this._cell.internalMetadata.isPaused) { + const runState = this._executionStateService.getCellExecutionState(this._cell.uri); + if (this._currentExecutingStateTimer && !runState?.didPause) { return; } - const runState = this._executionStateService.getCellExecutionState(this._cell.uri); const item = this._getItemForState(runState, this._cell.internalMetadata); // Show the execution spinner for a minimum time @@ -134,7 +134,7 @@ class ExecutionStateCellStatusBarItem extends Disposable { private _getItemForState(runState: ICellExecutionEntry | undefined, internalMetadata: NotebookCellInternalMetadata): INotebookCellStatusBarItem | undefined { const state = runState?.state; - const { lastRunSuccess, isPaused } = internalMetadata; + const { lastRunSuccess } = internalMetadata; if (!state && lastRunSuccess) { return { text: `$(${successStateIcon.id})`, @@ -159,7 +159,7 @@ class ExecutionStateCellStatusBarItem extends Disposable { priority: Number.MAX_SAFE_INTEGER }; } else if (state === NotebookCellExecutionState.Executing) { - const icon = isPaused ? + const icon = runState?.didPause ? executingStateIcon : ThemeIcon.modify(executingStateIcon, 'spin'); return { @@ -214,7 +214,7 @@ class TimerCellStatusBarItem extends Disposable { let item: INotebookCellStatusBarItem | undefined; const runState = this._executionStateService.getCellExecutionState(this._cell.uri); const state = runState?.state; - if (this._cell.internalMetadata.isPaused) { + if (runState?.didPause) { item = undefined; } else if (state === NotebookCellExecutionState.Executing) { const startTime = this._cell.internalMetadata.runStartTime; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts index 85caa5828507b..81786cc95db57 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts @@ -342,7 +342,6 @@ class CellExecution implements ICellExecutionEntry { lastRunSuccess: completionData.lastRunSuccess, runStartTime: this._didPause ? null : cellModel.internalMetadata.runStartTime, runEndTime: this._didPause ? null : completionData.runEndTime, - isPaused: false } }; this._applyExecutionEdits([edit]); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts index 415d8c9d330c9..c43e9993fa632 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts @@ -66,7 +66,7 @@ export class CellProgressBar extends CellPart { private _updateForExecutionState(element: ICellViewModel, e?: ICellExecutionStateChangedEvent): void { const exeState = e?.changed ?? this._notebookExecutionStateService.getCellExecutionState(element.uri); const progressBar = element.isInputCollapsed ? this._collapsedProgressBar : this._progressBar; - if (exeState?.state === NotebookCellExecutionState.Executing && !element.internalMetadata.isPaused) { + if (exeState?.state === NotebookCellExecutionState.Executing && !exeState.didPause) { showProgressBar(progressBar); } else { progressBar.hide(); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d0a73845257f1..75d402800ce2a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -104,7 +104,6 @@ export interface NotebookCellInternalMetadata { runStartTime?: number; runStartTimeAdjustment?: number; runEndTime?: number; - isPaused?: boolean; } export interface NotebookCellCollapseState { From ef9b27acc26e7f6ced58dbd5d21fc799d7fd61c5 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Jan 2022 15:14:16 +0100 Subject: [PATCH 1099/2210] Minor cleanup --- .../editor/contrib/hover/modesContentHover.ts | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index a4431655e4fda..604307cab6e43 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -10,7 +10,7 @@ import { Widget } from 'vs/base/browser/ui/widget'; import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Constants } from 'vs/base/common/uint'; import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -211,7 +211,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I private _isChangingDecorations: boolean; private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget | null; - private _renderDisposable: IDisposable | null; + private _renderDisposable: DisposableStore | null; private _preferAbove: boolean; constructor( @@ -365,7 +365,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I return true; } - private _showAt(position: Position, range: Range | null, focus: boolean): void { + private _showAt(node: DocumentFragment, position: Position, range: Range | null, focus: boolean): void { // Position has changed this._showAtPosition = position; this._showAtRange = range; @@ -381,6 +381,13 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I if (focus) { this._hover.containerDomNode.focus(); } + + this._hover.contentsDomNode.textContent = ''; + this._hover.contentsDomNode.appendChild(node); + this._updateFont(); + + this._editor.layoutContentWidget(this); + this._hover.onContentsChanged(); } public getPosition(): IContentWidgetPosition | null { @@ -410,15 +417,6 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I codeClasses.forEach(node => this._editor.applyFontInfo(node)); } - private _updateContents(node: Node): void { - this._hover.contentsDomNode.textContent = ''; - this._hover.contentsDomNode.appendChild(node); - this._updateFont(); - - this._editor.layoutContentWidget(this); - this._hover.onContentsChanged(); - } - private layout(): void { const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); const { fontSize, lineHeight } = this._editor.getOption(EditorOption.fontInfo); @@ -546,49 +544,41 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I let renderColumn = Constants.MAX_SAFE_SMALL_INTEGER; let highlightRange: Range = messages[0].range; let forceShowAtRange: Range | null = null; - let fragment = document.createDocumentFragment(); - - const disposables = new DisposableStore(); - const hoverParts = new Map(); + const groupedHoverParts = new Map(); for (const msg of messages) { renderColumn = Math.min(renderColumn, msg.range.startColumn); highlightRange = Range.plusRange(highlightRange, msg.range); - if (msg.forceShowAtRange) { forceShowAtRange = msg.range; } - if (!hoverParts.has(msg.owner)) { - hoverParts.set(msg.owner, []); + if (!groupedHoverParts.has(msg.owner)) { + groupedHoverParts.set(msg.owner, []); } - const dest = hoverParts.get(msg.owner)!; - dest.push(msg); + groupedHoverParts.get(msg.owner)!.push(msg); } - const statusBar = disposables.add(new EditorHoverStatusBar(this._keybindingService)); - + this._renderDisposable = new DisposableStore(); + const statusBar = this._renderDisposable.add(new EditorHoverStatusBar(this._keybindingService)); + const fragment = document.createDocumentFragment(); for (const participant of this._participants) { - if (hoverParts.has(participant)) { - const participantHoverParts = hoverParts.get(participant)!; - disposables.add(participant.renderHoverParts(participantHoverParts, fragment, statusBar)); + if (groupedHoverParts.has(participant)) { + const participantHoverParts = groupedHoverParts.get(participant)!; + this._renderDisposable.add(participant.renderHoverParts(participantHoverParts, fragment, statusBar)); } } - if (statusBar.hasContent) { fragment.appendChild(statusBar.hoverElement); } - this._renderDisposable = disposables; - // show if (fragment.hasChildNodes()) { if (forceShowAtRange) { - this._showAt(forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus); + this._showAt(fragment, forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus); } else { - this._showAt(new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus); + this._showAt(fragment, new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus); } - this._updateContents(fragment); } if (this._colorPicker) { this._colorPicker.layout(); From 18ccd3679369dd083c19cceb9f83b30fa10a8301 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Jan 2022 15:16:13 +0100 Subject: [PATCH 1100/2210] Remove broken workaround --- src/vs/editor/contrib/colorPicker/colorPicker.css | 4 ---- src/vs/editor/contrib/hover/modesContentHover.ts | 8 -------- 2 files changed, 12 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/colorPicker.css b/src/vs/editor/contrib/colorPicker/colorPicker.css index ee6b21f0ffbbb..6f0d47697a912 100644 --- a/src/vs/editor/contrib/colorPicker/colorPicker.css +++ b/src/vs/editor/contrib/colorPicker/colorPicker.css @@ -10,10 +10,6 @@ -ms-user-select: none; } -.monaco-editor .colorpicker-hover:focus { - outline: none; -} - /* Decoration */ .colorpicker-color-decoration { diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 604307cab6e43..4ca5494eac86c 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -278,14 +278,6 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I this._editor.getOption(EditorOption.hover).delay ); - this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.FOCUS, () => { - if (this._colorPicker) { - this.getDomNode().classList.add('colorpicker-hover'); - } - })); - this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.BLUR, () => { - this.getDomNode().classList.remove('colorpicker-hover'); - })); this._register(editor.onDidChangeConfiguration(() => { this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay); this._preferAbove = this._editor.getOption(EditorOption.hover).above; From 5582f9bbad8c56465e2b3ed3ae0d5d4fdd8e1348 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Jan 2022 15:28:24 +0100 Subject: [PATCH 1101/2210] More cleanup --- src/vs/editor/browser/editorBrowser.ts | 4 +- .../{modesContentHover.ts => contentHover.ts} | 75 ++++++++++--------- src/vs/editor/contrib/hover/hover.ts | 32 ++++---- .../{modesGlyphHover.ts => marginHover.ts} | 59 ++++++++------- src/vs/monaco.d.ts | 3 + 5 files changed, 87 insertions(+), 86 deletions(-) rename src/vs/editor/contrib/hover/{modesContentHover.ts => contentHover.ts} (96%) rename src/vs/editor/contrib/hover/{modesGlyphHover.ts => marginHover.ts} (94%) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index cf5b1e12502c3..8733ada2f7126 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -141,7 +141,9 @@ export interface IContentWidget { * Render this content widget in a location where it could overflow the editor's view dom node. */ allowEditorOverflow?: boolean; - + /** + * Call preventDefault() on mousedown events that target the content widget. + */ suppressMouseDown?: boolean; /** * Get a unique identifier of the content widget. diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts similarity index 96% rename from src/vs/editor/contrib/hover/modesContentHover.ts rename to src/vs/editor/contrib/hover/contentHover.ts index 4ca5494eac86c..06ef386f2e344 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -69,7 +69,7 @@ class EditorHoverStatusBar extends Disposable implements IEditorHoverStatusBar { } } -class ModesContentComputer implements IHoverComputer { +class ContentHoverComputer implements IHoverComputer { private readonly _editor: ICodeEditor; private _result: IHoverPart[]; @@ -130,7 +130,7 @@ class ModesContentComputer implements IHoverComputer { return AsyncIterableObject.EMPTY; } - const lineDecorations = ModesContentComputer._getLineDecorations(this._editor, anchor); + const lineDecorations = ContentHoverComputer._getLineDecorations(this._editor, anchor); return AsyncIterableObject.merge( this._participants.map(participant => this._computeAsync(participant, lineDecorations, anchor, token)) ); @@ -148,7 +148,7 @@ class ModesContentComputer implements IHoverComputer { return []; } - const lineDecorations = ModesContentComputer._getLineDecorations(this._editor, this._anchor); + const lineDecorations = ContentHoverComputer._getLineDecorations(this._editor, this._anchor); let result: IHoverPart[] = []; for (const participant of this._participants) { @@ -186,7 +186,7 @@ class ModesContentComputer implements IHoverComputer { } } -export class ModesContentHoverWidget extends Widget implements IContentWidget, IEditorHover { +export class ContentHoverWidget extends Widget implements IContentWidget, IEditorHover { static readonly ID = 'editor.contrib.modesContentHoverWidget'; @@ -205,7 +205,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I private _messages: IHoverPart[]; private _messagesAreComplete: boolean; private _lastAnchor: HoverAnchor | null; - private readonly _computer: ModesContentComputer; + private readonly _computer: ContentHoverComputer; private readonly _hoverOperation: HoverOperation; private _highlightDecorations: string[]; private _isChangingDecorations: boolean; @@ -252,9 +252,9 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I } })); - this._editor.onDidLayoutChange(() => this.layout()); + this._editor.onDidLayoutChange(() => this._layout()); - this.layout(); + this._layout(); this._editor.addContentWidget(this); this._showAtPosition = null; this._showAtRange = null; @@ -263,7 +263,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I this._messages = []; this._messagesAreComplete = false; this._lastAnchor = null; - this._computer = new ModesContentComputer(this._editor, this._participants); + this._computer = new ContentHoverComputer(this._editor, this._participants); this._highlightDecorations = []; this._isChangingDecorations = false; this._shouldFocus = false; @@ -278,6 +278,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I this._editor.getOption(EditorOption.hover).delay ); + this._register(editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); this._register(editor.onDidChangeConfiguration(() => { this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay); this._preferAbove = this._editor.getOption(EditorOption.hover).above; @@ -297,7 +298,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I } public getId(): string { - return ModesContentHoverWidget.ID; + return ContentHoverWidget.ID; } public getDomNode(): HTMLElement { @@ -357,31 +358,6 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I return true; } - private _showAt(node: DocumentFragment, position: Position, range: Range | null, focus: boolean): void { - // Position has changed - this._showAtPosition = position; - this._showAtRange = range; - this._hoverVisibleKey.set(true); - this._isVisible = true; - this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - - this._editor.layoutContentWidget(this); - // Simply force a synchronous render on the editor - // such that the widget does not really render with left = '0px' - this._editor.render(); - this._stoleFocus = focus; - if (focus) { - this._hover.containerDomNode.focus(); - } - - this._hover.contentsDomNode.textContent = ''; - this._hover.contentsDomNode.appendChild(node); - this._updateFont(); - - this._editor.layoutContentWidget(this); - this._hover.onContentsChanged(); - } - public getPosition(): IContentWidgetPosition | null { if (this._isVisible) { let preferAbove = this._preferAbove; @@ -409,7 +385,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I codeClasses.forEach(node => this._editor.applyFontInfo(node)); } - private layout(): void { + private _layout(): void { const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); const { fontSize, lineHeight } = this._editor.getOption(EditorOption.fontInfo); @@ -419,7 +395,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I this._hover.contentsDomNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; } - public onModelDecorationsChanged(): void { + private _onModelDecorationsChanged(): void { if (this._isChangingDecorations) { return; } @@ -579,11 +555,36 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I this._isChangingDecorations = true; this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, highlightRange ? [{ range: highlightRange, - options: ModesContentHoverWidget._DECORATION_OPTIONS + options: ContentHoverWidget._DECORATION_OPTIONS }] : []); this._isChangingDecorations = false; } + private _showAt(node: DocumentFragment, position: Position, range: Range | null, focus: boolean): void { + // Position has changed + this._showAtPosition = position; + this._showAtRange = range; + this._hoverVisibleKey.set(true); + this._isVisible = true; + this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); + + this._editor.layoutContentWidget(this); + // Simply force a synchronous render on the editor + // such that the widget does not really render with left = '0px' + this._editor.render(); + this._stoleFocus = focus; + if (focus) { + this._hover.containerDomNode.focus(); + } + + this._hover.contentsDomNode.textContent = ''; + this._hover.contentsDomNode.appendChild(node); + this._updateFont(); + + this._editor.layoutContentWidget(this); + this._hover.onContentsChanged(); + } + private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ description: 'content-hover-highlight', className: 'hoverHighlight' diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 018a58d3d2f45..5ed9d1fe1872a 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -15,8 +15,8 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageService } from 'vs/editor/common/services/language'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; -import { ModesContentHoverWidget } from 'vs/editor/contrib/hover/modesContentHover'; -import { ModesGlyphHoverWidget } from 'vs/editor/contrib/hover/modesGlyphHover'; +import { ContentHoverWidget } from 'vs/editor/contrib/hover/contentHover'; +import { MarginHoverWidget } from 'vs/editor/contrib/hover/marginHover'; import * as nls from 'vs/nls'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -33,8 +33,8 @@ export class ModesHoverController implements IEditorContribution { private readonly _toUnhook = new DisposableStore(); private readonly _didChangeConfigurationHandler: IDisposable; - private _contentWidget: ModesContentHoverWidget | null; - private _glyphWidget: ModesGlyphHoverWidget | null; + private _contentWidget: ContentHoverWidget | null; + private _glyphWidget: MarginHoverWidget | null; private _isMouseDown: boolean; private _hoverClicked: boolean; @@ -81,7 +81,6 @@ export class ModesHoverController implements IEditorContribution { this._toUnhook.add(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); this._toUnhook.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); this._toUnhook.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); - this._toUnhook.add(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); } else { this._toUnhook.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); this._toUnhook.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); @@ -96,11 +95,6 @@ export class ModesHoverController implements IEditorContribution { this._toUnhook.clear(); } - private _onModelDecorationsChanged(): void { - this._contentWidget?.onModelDecorationsChanged(); - this._glyphWidget?.onModelDecorationsChanged(); - } - private _onEditorScrollChanged(e: IScrollEvent): void { if (e.scrollTopChanged || e.scrollLeftChanged) { this._hideWidgets(); @@ -112,18 +106,18 @@ export class ModesHoverController implements IEditorContribution { const targetType = mouseEvent.target.type; - if (targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID) { + if (targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ContentHoverWidget.ID) { this._hoverClicked = true; // mouse down on top of content hover widget return; } - if (targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID) { + if (targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === MarginHoverWidget.ID) { // mouse down on top of overlay hover widget return; } - if (targetType !== MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail !== ModesGlyphHoverWidget.ID) { + if (targetType !== MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail !== MarginHoverWidget.ID) { this._hoverClicked = false; } @@ -141,7 +135,7 @@ export class ModesHoverController implements IEditorContribution { return; } - if (this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID) { + if (this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ContentHoverWidget.ID) { // mouse moved on top of content hover widget return; } @@ -152,14 +146,14 @@ export class ModesHoverController implements IEditorContribution { } if ( - !this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID + !this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ContentHoverWidget.ID && this._contentWidget?.isColorPickerVisible() ) { // though the hover is not sticky, the color picker needs to. return; } - if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID) { + if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === MarginHoverWidget.ID) { // mouse moved on top of overlay hover widget return; } @@ -178,7 +172,7 @@ export class ModesHoverController implements IEditorContribution { if (targetType === MouseTargetType.GUTTER_GLYPH_MARGIN && mouseEvent.target.position) { this._contentWidget?.hide(); if (!this._glyphWidget) { - this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._languageService, this._openerService); + this._glyphWidget = new MarginHoverWidget(this._editor, this._languageService, this._openerService); } this._glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber); return; @@ -204,9 +198,9 @@ export class ModesHoverController implements IEditorContribution { this._contentWidget?.hide(); } - private _getOrCreateContentWidget(): ModesContentHoverWidget { + private _getOrCreateContentWidget(): ContentHoverWidget { if (!this._contentWidget) { - this._contentWidget = this._instantiationService.createInstance(ModesContentHoverWidget, this._editor, this._hoverVisibleKey); + this._contentWidget = this._instantiationService.createInstance(ContentHoverWidget, this._editor, this._hoverVisibleKey); } return this._contentWidget; } diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/marginHover.ts similarity index 94% rename from src/vs/editor/contrib/hover/modesGlyphHover.ts rename to src/vs/editor/contrib/hover/marginHover.ts index 922fb82cc7661..3c83a7196b012 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/marginHover.ts @@ -22,7 +22,7 @@ export interface IHoverMessage { value: IMarkdownString; } -class MarginComputer implements IHoverComputer { +class MarginHoverComputer implements IHoverComputer { private readonly _editor: ICodeEditor; private _lineNumber: number; @@ -87,7 +87,7 @@ class MarginComputer implements IHoverComputer { } } -export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { +export class MarginHoverWidget extends Widget implements IOverlayWidget { public static readonly ID = 'editor.contrib.modesGlyphHoverWidget'; @@ -99,7 +99,7 @@ export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { private _lastLineNumber: number; private readonly _markdownRenderer: MarkdownRenderer; - private readonly _computer: MarginComputer; + private readonly _computer: MarginHoverComputer; private readonly _hoverOperation: HoverOperation; private readonly _renderDisposeables = this._register(new DisposableStore()); @@ -119,7 +119,7 @@ export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); this._markdownRenderer = this._register(new MarkdownRenderer({ editor: this._editor }, languageService, openerService)); - this._computer = new MarginComputer(this._editor); + this._computer = new MarginHoverComputer(this._editor); this._hoverOperation = new HoverOperation( this._computer, (result: IHoverMessage[]) => this._withResult(result), @@ -128,6 +128,7 @@ export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { 300 ); + this._register(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (e.hasChanged(EditorOption.fontInfo)) { this._updateFont(); @@ -144,7 +145,7 @@ export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { } public getId(): string { - return ModesGlyphHoverWidget.ID; + return MarginHoverWidget.ID; } public getDomNode(): HTMLElement { @@ -155,35 +156,12 @@ export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { return null; } - private _showAt(lineNumber: number): void { - if (!this._isVisible) { - this._isVisible = true; - this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - } - - const editorLayout = this._editor.getLayoutInfo(); - const topForLineNumber = this._editor.getTopForLineNumber(lineNumber); - const editorScrollTop = this._editor.getScrollTop(); - const lineHeight = this._editor.getOption(EditorOption.lineHeight); - const nodeHeight = this._hover.containerDomNode.clientHeight; - const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2); - - this._hover.containerDomNode.style.left = `${editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth}px`; - this._hover.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`; - } - private _updateFont(): void { const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._hover.contentsDomNode.getElementsByClassName('code')); codeClasses.forEach(node => this._editor.applyFontInfo(node)); } - private _updateContents(node: Node): void { - this._hover.contentsDomNode.textContent = ''; - this._hover.contentsDomNode.appendChild(node); - this._updateFont(); - } - - public onModelDecorationsChanged(): void { + private _onModelDecorationsChanged(): void { if (this._isVisible) { // The decorations have changed and the hover is visible, // we need to recompute the displayed text @@ -244,4 +222,27 @@ export class ModesGlyphHoverWidget extends Widget implements IOverlayWidget { this._updateContents(fragment); this._showAt(lineNumber); } + + private _updateContents(node: Node): void { + this._hover.contentsDomNode.textContent = ''; + this._hover.contentsDomNode.appendChild(node); + this._updateFont(); + } + + private _showAt(lineNumber: number): void { + if (!this._isVisible) { + this._isVisible = true; + this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); + } + + const editorLayout = this._editor.getLayoutInfo(); + const topForLineNumber = this._editor.getTopForLineNumber(lineNumber); + const editorScrollTop = this._editor.getScrollTop(); + const lineHeight = this._editor.getOption(EditorOption.lineHeight); + const nodeHeight = this._hover.containerDomNode.clientHeight; + const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2); + + this._hover.containerDomNode.style.left = `${editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth}px`; + this._hover.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`; + } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 9e03ba62ae9e3..8b2707af5f257 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4542,6 +4542,9 @@ declare namespace monaco.editor { * Render this content widget in a location where it could overflow the editor's view dom node. */ allowEditorOverflow?: boolean; + /** + * Call preventDefault() on mousedown events that target the content widget. + */ suppressMouseDown?: boolean; /** * Get a unique identifier of the content widget. From 93f987a8a2978533843e8a7585d5eebd63d58980 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Jan 2022 16:21:13 +0100 Subject: [PATCH 1102/2210] Extract `ContentHoverController` --- src/vs/editor/contrib/hover/contentHover.ts | 602 ++++++++++---------- src/vs/editor/contrib/hover/hover.ts | 14 +- 2 files changed, 313 insertions(+), 303 deletions(-) diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index 06ef386f2e344..1bfbccc8ead98 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { HoverAction, HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; -import { Widget } from 'vs/base/browser/ui/widget'; import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -27,180 +25,21 @@ import { HoverAnchor, HoverAnchorType, HoverRangeAnchor, IEditorHover, IEditorHo import { MarkdownHoverParticipant } from 'vs/editor/contrib/hover/markdownHoverParticipant'; import { MarkerHoverParticipant } from 'vs/editor/contrib/hover/markerHoverParticipant'; import { InlineCompletionsHoverParticipant } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest'; import { UnicodeHighlighterHoverParticipant } from 'vs/editor/contrib/unicodeHighlighter/unicodeHighlighter'; import { AsyncIterableObject } from 'vs/base/common/async'; import { InlayHintsHover } from 'vs/editor/contrib/inlayHints/inlayHintsHover'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; const $ = dom.$; -class EditorHoverStatusBar extends Disposable implements IEditorHoverStatusBar { - - public readonly hoverElement: HTMLElement; - private readonly actionsElement: HTMLElement; - private _hasContent: boolean = false; - - public get hasContent() { - return this._hasContent; - } - - constructor( - @IKeybindingService private readonly _keybindingService: IKeybindingService, - ) { - super(); - this.hoverElement = $('div.hover-row.status-bar'); - this.actionsElement = dom.append(this.hoverElement, $('div.actions')); - } - - public addAction(actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }): IEditorHoverAction { - const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId); - const keybindingLabel = keybinding ? keybinding.getLabel() : null; - this._hasContent = true; - return this._register(HoverAction.render(this.actionsElement, actionOptions, keybindingLabel)); - } - - public append(element: HTMLElement): HTMLElement { - const result = dom.append(this.actionsElement, element); - this._hasContent = true; - return result; - } -} - -class ContentHoverComputer implements IHoverComputer { - - private readonly _editor: ICodeEditor; - private _result: IHoverPart[]; - private _anchor: HoverAnchor | null; - - constructor( - editor: ICodeEditor, - private readonly _participants: readonly IEditorHoverParticipant[] - ) { - this._editor = editor; - this._result = []; - this._anchor = null; - } - - public setAnchor(anchor: HoverAnchor): void { - this._anchor = anchor; - this._result = []; - } - - public clearResult(): void { - this._result = []; - } - - private static _getLineDecorations(editor: IActiveCodeEditor, anchor: HoverAnchor): IModelDecoration[] { - if (anchor.type !== HoverAnchorType.Range) { - return []; - } - - const model = editor.getModel(); - const lineNumber = anchor.range.startLineNumber; - const maxColumn = model.getLineMaxColumn(lineNumber); - return editor.getLineDecorations(lineNumber).filter((d) => { - if (d.options.isWholeLine) { - return true; - } - - const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1; - const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn; - if (d.options.showIfCollapsed) { - // Relax check around `showIfCollapsed` decorations to also include +/- 1 character - if (startColumn > anchor.range.startColumn + 1 || anchor.range.endColumn - 1 > endColumn) { - return false; - } - } else { - if (startColumn > anchor.range.startColumn || anchor.range.endColumn > endColumn) { - return false; - } - } - - return true; - }); - } - - public computeAsync(token: CancellationToken): AsyncIterableObject { - const anchor = this._anchor; - - if (!this._editor.hasModel() || !anchor) { - return AsyncIterableObject.EMPTY; - } - - const lineDecorations = ContentHoverComputer._getLineDecorations(this._editor, anchor); - return AsyncIterableObject.merge( - this._participants.map(participant => this._computeAsync(participant, lineDecorations, anchor, token)) - ); - } - - private _computeAsync(participant: IEditorHoverParticipant, lineDecorations: IModelDecoration[], anchor: HoverAnchor, token: CancellationToken): AsyncIterableObject { - if (!participant.computeAsync) { - return AsyncIterableObject.EMPTY; - } - return participant.computeAsync(anchor, lineDecorations, token); - } - - public computeSync(): IHoverPart[] { - if (!this._editor.hasModel() || !this._anchor) { - return []; - } - - const lineDecorations = ContentHoverComputer._getLineDecorations(this._editor, this._anchor); - - let result: IHoverPart[] = []; - for (const participant of this._participants) { - result = result.concat(participant.computeSync(this._anchor, lineDecorations)); - } - - return coalesce(result); - } - - public onResult(result: IHoverPart[], isFromSynchronousComputation: boolean): void { - // Always put synchronous messages before asynchronous ones - if (isFromSynchronousComputation) { - this._result = result.concat(this._result); - } else { - this._result = this._result.concat(result); - } - } - - public getResult(): IHoverPart[] { - return this._result.slice(0); - } - - public getResultWithLoadingMessage(): IHoverPart[] { - if (this._anchor) { - for (const participant of this._participants) { - if (participant.createLoadingMessage) { - const loadingMessage = participant.createLoadingMessage(this._anchor); - if (loadingMessage) { - return this._result.slice(0).concat([loadingMessage]); - } - } - } - } - return this._result.slice(0); - } -} - -export class ContentHoverWidget extends Widget implements IContentWidget, IEditorHover { - - static readonly ID = 'editor.contrib.modesContentHoverWidget'; +export class ContentHoverController extends Disposable implements IEditorHover { private readonly _participants: IEditorHoverParticipant[]; - - private readonly _hover: HoverWidget; - private readonly _editor: ICodeEditor; - private _isVisible: boolean; - private _showAtPosition: Position | null; - private _showAtRange: Range | null; - private _stoleFocus: boolean; - - // IContentWidget.allowEditorOverflow - public readonly allowEditorOverflow = true; + private readonly _widget: ContentHoverWidget; private _messages: IHoverPart[]; private _messagesAreComplete: boolean; @@ -212,53 +51,23 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget | null; private _renderDisposable: DisposableStore | null; - private _preferAbove: boolean; constructor( - editor: ICodeEditor, - private readonly _hoverVisibleKey: IContextKey, + private readonly _editor: ICodeEditor, @IInstantiationService instantiationService: IInstantiationService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { super(); this._participants = [ - instantiationService.createInstance(ColorHoverParticipant, editor, this), - instantiationService.createInstance(MarkdownHoverParticipant, editor, this), - instantiationService.createInstance(InlineCompletionsHoverParticipant, editor, this), - instantiationService.createInstance(UnicodeHighlighterHoverParticipant, editor, this), - instantiationService.createInstance(MarkerHoverParticipant, editor, this), - instantiationService.createInstance(InlayHintsHover, editor, this), + instantiationService.createInstance(ColorHoverParticipant, this._editor, this), + instantiationService.createInstance(MarkdownHoverParticipant, this._editor, this), + instantiationService.createInstance(InlineCompletionsHoverParticipant, this._editor, this), + instantiationService.createInstance(UnicodeHighlighterHoverParticipant, this._editor, this), + instantiationService.createInstance(MarkerHoverParticipant, this._editor, this), + instantiationService.createInstance(InlayHintsHover, this._editor, this), ]; - - this._editor = editor; - this._isVisible = false; - this._stoleFocus = false; - this._renderDisposable = null; - - this._hover = this._register(new HoverWidget()); - this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - - this.onkeydown(this._hover.containerDomNode, (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Escape)) { - this.hide(); - } - }); - - this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { - if (e.hasChanged(EditorOption.fontInfo)) { - this._updateFont(); - } - })); - - this._editor.onDidLayoutChange(() => this._layout()); - - this._layout(); - this._editor.addContentWidget(this); - this._showAtPosition = null; - this._showAtRange = null; - this._stoleFocus = false; + this._widget = this._register(instantiationService.createInstance(ContentHoverWidget, this._editor)); this._messages = []; this._messagesAreComplete = false; @@ -268,7 +77,7 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito this._isChangingDecorations = false; this._shouldFocus = false; this._colorPicker = null; - this._preferAbove = this._editor.getOption(EditorOption.hover).above; + this._renderDisposable = null; this._hoverOperation = new HoverOperation( this._computer, @@ -278,14 +87,18 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito this._editor.getOption(EditorOption.hover).delay ); - this._register(editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); - this._register(editor.onDidChangeConfiguration(() => { + this._register(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); + this._register(this._editor.onDidChangeConfiguration(() => { this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay); - this._preferAbove = this._editor.getOption(EditorOption.hover).above; + })); + this._register(dom.addStandardDisposableListener(this._widget.getDomNode(), 'keydown', (e) => { + if (e.equals(KeyCode.Escape)) { + this.hide(); + } })); this._register(TokenizationRegistry.onDidChange(() => { - if (this._isVisible && this._lastAnchor && this._messages.length > 0) { - this._hover.contentsDomNode.textContent = ''; + if (this._widget.position && this._lastAnchor && this._messages.length > 0) { + this._widget.clear(); this._renderMessages(this._lastAnchor, this._messages); } })); @@ -293,18 +106,9 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito public override dispose(): void { this._hoverOperation.cancel(); - this._editor.removeContentWidget(this); super.dispose(); } - public getId(): string { - return ContentHoverWidget.ID; - } - - public getDomNode(): HTMLElement { - return this._hover.containerDomNode; - } - private _shouldShowAt(mouseEvent: IEditorMouseEvent): boolean { const targetType = mouseEvent.target.type; if (targetType === MouseTargetType.CONTENT_TEXT) { @@ -327,7 +131,7 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito const anchorCandidates: HoverAnchor[] = []; for (const participant of this._participants) { - if (typeof participant.suggestHoverAnchor === 'function') { + if (participant.suggestHoverAnchor) { const anchor = participant.suggestHoverAnchor(mouseEvent); if (anchor) { anchorCandidates.push(anchor); @@ -358,48 +162,11 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito return true; } - public getPosition(): IContentWidgetPosition | null { - if (this._isVisible) { - let preferAbove = this._preferAbove; - if (!preferAbove && this._contextKeyService.getContextKeyValue(SuggestContext.Visible.key)) { - // Prefer rendering above if the suggest widget is visible - preferAbove = true; - } - return { - position: this._showAtPosition, - range: this._showAtRange, - preference: preferAbove ? [ - ContentWidgetPositionPreference.ABOVE, - ContentWidgetPositionPreference.BELOW, - ] : [ - ContentWidgetPositionPreference.BELOW, - ContentWidgetPositionPreference.ABOVE, - ], - }; - } - return null; - } - - private _updateFont(): void { - const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._hover.contentsDomNode.getElementsByClassName('code')); - codeClasses.forEach(node => this._editor.applyFontInfo(node)); - } - - private _layout(): void { - const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); - const { fontSize, lineHeight } = this._editor.getOption(EditorOption.fontInfo); - - this._hover.contentsDomNode.style.fontSize = `${fontSize}px`; - this._hover.contentsDomNode.style.lineHeight = `${lineHeight / fontSize}`; - this._hover.contentsDomNode.style.maxHeight = `${height}px`; - this._hover.contentsDomNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; - } - private _onModelDecorationsChanged(): void { if (this._isChangingDecorations) { return; } - if (this._isVisible) { + if (this._widget.position) { // The decorations have changed and the hover is visible, // we need to recompute the displayed text this._hoverOperation.cancel(); @@ -423,11 +190,11 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito this._hoverOperation.cancel(); - if (this._isVisible) { + if (this._widget.position) { // The range might have changed, but the hover is visible // Instead of hiding it completely, filter out messages that are still in the new range and // kick off a new computation - if (!this._showAtPosition || !this._lastAnchor || !anchor.canAdoptVisibleHover(this._lastAnchor, this._showAtPosition)) { + if (!this._lastAnchor || !anchor.canAdoptVisibleHover(this._lastAnchor, this._widget.position)) { this.hide(); } else { const filteredMessages = this._messages.filter((m) => m.isValidForHoverAnchor(anchor)); @@ -452,21 +219,7 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito this._lastAnchor = null; this._hoverOperation.cancel(); - if (this._isVisible) { - setTimeout(() => { - // Give commands a chance to see the key - if (!this._isVisible) { - this._hoverVisibleKey.set(false); - } - }, 0); - this._isVisible = false; - this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - - this._editor.layoutContentWidget(this); - if (this._stoleFocus) { - this._editor.focus(); - } - } + this._widget.hide(); this._isChangingDecorations = true; this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, []); @@ -487,7 +240,7 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito } public onContentsChanged(): void { - this._hover.onContentsChanged(); + this._widget.onContentsChanged(); } private _withResult(result: IHoverPart[], complete: boolean): void { @@ -543,9 +296,9 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito if (fragment.hasChildNodes()) { if (forceShowAtRange) { - this._showAt(fragment, forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus); + this._widget.showAt(fragment, forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus); } else { - this._showAt(fragment, new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus); + this._widget.showAt(fragment, new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus); } } if (this._colorPicker) { @@ -555,38 +308,299 @@ export class ContentHoverWidget extends Widget implements IContentWidget, IEdito this._isChangingDecorations = true; this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, highlightRange ? [{ range: highlightRange, - options: ContentHoverWidget._DECORATION_OPTIONS + options: ContentHoverController._DECORATION_OPTIONS }] : []); this._isChangingDecorations = false; } - private _showAt(node: DocumentFragment, position: Position, range: Range | null, focus: boolean): void { - // Position has changed - this._showAtPosition = position; - this._showAtRange = range; - this._hoverVisibleKey.set(true); - this._isVisible = true; - this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); + private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ + description: 'content-hover-highlight', + className: 'hoverHighlight' + }); +} + +class ContentHoverVisibleData { + constructor( + public readonly showAtPosition: Position | null, + public readonly showAtRange: Range | null, + public readonly preferAbove: boolean, + public readonly stoleFocus: boolean, + ) { } +} + +export class ContentHoverWidget extends Disposable implements IContentWidget { + + static readonly ID = 'editor.contrib.contentHoverWidget'; + + public readonly allowEditorOverflow = true; + private readonly _hoverVisibleKey = EditorContextKeys.hoverVisible.bindTo(this._contextKeyService); + private readonly _hover: HoverWidget = this._register(new HoverWidget()); + + private _visibleData: ContentHoverVisibleData | null = null; + + /** + * Returns `null` if the hover is not visible. + */ + public get position(): Position | null { + return this._visibleData?.showAtPosition ?? null; + } + + constructor( + private readonly _editor: ICodeEditor, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + ) { + super(); + + this._hoverVisibleKey.set(!!this._visibleData); + this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); + + this._register(this._editor.onDidLayoutChange(() => this._layout())); + this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (e.hasChanged(EditorOption.fontInfo)) { + this._updateFont(); + } + })); + + this._layout(); + this._editor.addContentWidget(this); + } + + public override dispose(): void { + this._editor.removeContentWidget(this); + super.dispose(); + } + + public getId(): string { + return ContentHoverWidget.ID; + } + + public getDomNode(): HTMLElement { + return this._hover.containerDomNode; + } + + public getPosition(): IContentWidgetPosition | null { + if (!this._visibleData) { + return null; + } + let preferAbove = this._visibleData.preferAbove; + if (!preferAbove && this._contextKeyService.getContextKeyValue(SuggestContext.Visible.key)) { + // Prefer rendering above if the suggest widget is visible + preferAbove = true; + } + return { + position: this._visibleData.showAtPosition, + range: this._visibleData.showAtRange, + preference: ( + preferAbove + ? [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW] + : [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] + ), + }; + } + + private _layout(): void { + const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); + const { fontSize, lineHeight } = this._editor.getOption(EditorOption.fontInfo); + + this._hover.contentsDomNode.style.fontSize = `${fontSize}px`; + this._hover.contentsDomNode.style.lineHeight = `${lineHeight / fontSize}`; + this._hover.contentsDomNode.style.maxHeight = `${height}px`; + this._hover.contentsDomNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; + } + + private _updateFont(): void { + const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._hover.contentsDomNode.getElementsByClassName('code')); + codeClasses.forEach(node => this._editor.applyFontInfo(node)); + } + + public showAt(node: DocumentFragment, position: Position, range: Range | null, focus: boolean): void { + this._visibleData = new ContentHoverVisibleData(position, range, this._editor.getOption(EditorOption.hover).above, focus); + this._hoverVisibleKey.set(!!this._visibleData); + this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); + + this._hover.contentsDomNode.textContent = ''; + this._hover.contentsDomNode.appendChild(node); + this._updateFont(); this._editor.layoutContentWidget(this); + this._hover.onContentsChanged(); + // Simply force a synchronous render on the editor // such that the widget does not really render with left = '0px' this._editor.render(); - this._stoleFocus = focus; if (focus) { this._hover.containerDomNode.focus(); } + } - this._hover.contentsDomNode.textContent = ''; - this._hover.contentsDomNode.appendChild(node); - this._updateFont(); + public hide(): void { + if (this._visibleData) { + const stoleFocus = this._visibleData.stoleFocus; + this._visibleData = null; + this._hoverVisibleKey.set(!!this._visibleData); + this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); - this._editor.layoutContentWidget(this); + this._editor.layoutContentWidget(this); + if (stoleFocus) { + this._editor.focus(); + } + } + } + + public onContentsChanged(): void { this._hover.onContentsChanged(); } - private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ - description: 'content-hover-highlight', - className: 'hoverHighlight' - }); + public clear(): void { + this._hover.contentsDomNode.textContent = ''; + } +} + +class EditorHoverStatusBar extends Disposable implements IEditorHoverStatusBar { + + public readonly hoverElement: HTMLElement; + private readonly actionsElement: HTMLElement; + private _hasContent: boolean = false; + + public get hasContent() { + return this._hasContent; + } + + constructor( + @IKeybindingService private readonly _keybindingService: IKeybindingService, + ) { + super(); + this.hoverElement = $('div.hover-row.status-bar'); + this.actionsElement = dom.append(this.hoverElement, $('div.actions')); + } + + public addAction(actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }): IEditorHoverAction { + const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId); + const keybindingLabel = keybinding ? keybinding.getLabel() : null; + this._hasContent = true; + return this._register(HoverAction.render(this.actionsElement, actionOptions, keybindingLabel)); + } + + public append(element: HTMLElement): HTMLElement { + const result = dom.append(this.actionsElement, element); + this._hasContent = true; + return result; + } +} + +class ContentHoverComputer implements IHoverComputer { + + private readonly _editor: ICodeEditor; + private _result: IHoverPart[]; + private _anchor: HoverAnchor | null; + + constructor( + editor: ICodeEditor, + private readonly _participants: readonly IEditorHoverParticipant[] + ) { + this._editor = editor; + this._result = []; + this._anchor = null; + } + + public setAnchor(anchor: HoverAnchor): void { + this._anchor = anchor; + this._result = []; + } + + public clearResult(): void { + this._result = []; + } + + private static _getLineDecorations(editor: IActiveCodeEditor, anchor: HoverAnchor): IModelDecoration[] { + if (anchor.type !== HoverAnchorType.Range) { + return []; + } + + const model = editor.getModel(); + const lineNumber = anchor.range.startLineNumber; + const maxColumn = model.getLineMaxColumn(lineNumber); + return editor.getLineDecorations(lineNumber).filter((d) => { + if (d.options.isWholeLine) { + return true; + } + + const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1; + const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn; + if (d.options.showIfCollapsed) { + // Relax check around `showIfCollapsed` decorations to also include +/- 1 character + if (startColumn > anchor.range.startColumn + 1 || anchor.range.endColumn - 1 > endColumn) { + return false; + } + } else { + if (startColumn > anchor.range.startColumn || anchor.range.endColumn > endColumn) { + return false; + } + } + + return true; + }); + } + + public computeAsync(token: CancellationToken): AsyncIterableObject { + const anchor = this._anchor; + + if (!this._editor.hasModel() || !anchor) { + return AsyncIterableObject.EMPTY; + } + + const lineDecorations = ContentHoverComputer._getLineDecorations(this._editor, anchor); + return AsyncIterableObject.merge( + this._participants.map(participant => this._computeAsync(participant, lineDecorations, anchor, token)) + ); + } + + private _computeAsync(participant: IEditorHoverParticipant, lineDecorations: IModelDecoration[], anchor: HoverAnchor, token: CancellationToken): AsyncIterableObject { + if (!participant.computeAsync) { + return AsyncIterableObject.EMPTY; + } + return participant.computeAsync(anchor, lineDecorations, token); + } + + public computeSync(): IHoverPart[] { + if (!this._editor.hasModel() || !this._anchor) { + return []; + } + + const lineDecorations = ContentHoverComputer._getLineDecorations(this._editor, this._anchor); + + let result: IHoverPart[] = []; + for (const participant of this._participants) { + result = result.concat(participant.computeSync(this._anchor, lineDecorations)); + } + + return coalesce(result); + } + + public onResult(result: IHoverPart[], isFromSynchronousComputation: boolean): void { + // Always put synchronous messages before asynchronous ones + if (isFromSynchronousComputation) { + this._result = result.concat(this._result); + } else { + this._result = this._result.concat(result); + } + } + + public getResult(): IHoverPart[] { + return this._result.slice(0); + } + + public getResultWithLoadingMessage(): IHoverPart[] { + if (this._anchor) { + for (const participant of this._participants) { + if (participant.createLoadingMessage) { + const loadingMessage = participant.createLoadingMessage(this._anchor); + if (loadingMessage) { + return this._result.slice(0).concat([loadingMessage]); + } + } + } + } + return this._result.slice(0); + } } diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 5ed9d1fe1872a..56c389e947cda 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -15,11 +15,11 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageService } from 'vs/editor/common/services/language'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; -import { ContentHoverWidget } from 'vs/editor/contrib/hover/contentHover'; +import { ContentHoverWidget, ContentHoverController } from 'vs/editor/contrib/hover/contentHover'; import { MarginHoverWidget } from 'vs/editor/contrib/hover/marginHover'; import * as nls from 'vs/nls'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -33,7 +33,7 @@ export class ModesHoverController implements IEditorContribution { private readonly _toUnhook = new DisposableStore(); private readonly _didChangeConfigurationHandler: IDisposable; - private _contentWidget: ContentHoverWidget | null; + private _contentWidget: ContentHoverController | null; private _glyphWidget: MarginHoverWidget | null; private _isMouseDown: boolean; @@ -41,8 +41,6 @@ export class ModesHoverController implements IEditorContribution { private _isHoverEnabled!: boolean; private _isHoverSticky!: boolean; - private _hoverVisibleKey: IContextKey; - static get(editor: ICodeEditor): ModesHoverController | null { return editor.getContribution(ModesHoverController.ID); } @@ -66,8 +64,6 @@ export class ModesHoverController implements IEditorContribution { this._hookEvents(); } }); - - this._hoverVisibleKey = EditorContextKeys.hoverVisible.bindTo(_contextKeyService); } private _hookEvents(): void { @@ -198,9 +194,9 @@ export class ModesHoverController implements IEditorContribution { this._contentWidget?.hide(); } - private _getOrCreateContentWidget(): ContentHoverWidget { + private _getOrCreateContentWidget(): ContentHoverController { if (!this._contentWidget) { - this._contentWidget = this._instantiationService.createInstance(ContentHoverWidget, this._editor, this._hoverVisibleKey); + this._contentWidget = this._instantiationService.createInstance(ContentHoverController, this._editor); } return this._contentWidget; } From 616359498f75b3676450e3d374e4265c04ecb6e4 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Jan 2022 20:49:20 +0100 Subject: [PATCH 1103/2210] Reduce interactions around color picker --- .../contrib/hover/colorHoverParticipant.ts | 9 ++-- src/vs/editor/contrib/hover/contentHover.ts | 48 +++++++++++-------- src/vs/editor/contrib/hover/hoverOperation.ts | 12 ++--- src/vs/editor/contrib/hover/hoverTypes.ts | 9 +++- .../contrib/hover/markdownHoverParticipant.ts | 6 +-- .../contrib/hover/markerHoverParticipant.ts | 8 ++-- .../inlineCompletionsHoverParticipant.ts | 14 +++--- .../unicodeHighlighter/unicodeHighlighter.ts | 6 +-- 8 files changed, 63 insertions(+), 49 deletions(-) diff --git a/src/vs/editor/contrib/hover/colorHoverParticipant.ts b/src/vs/editor/contrib/hover/colorHoverParticipant.ts index b4dd93c711c18..d9654d8972999 100644 --- a/src/vs/editor/contrib/hover/colorHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/colorHoverParticipant.ts @@ -16,7 +16,7 @@ import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color'; import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel'; import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget'; -import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import { IThemeService } from 'vs/platform/theme/common/themeService'; export class ColorHover implements IHoverPart { @@ -96,7 +96,7 @@ export class ColorHoverParticipant implements IEditorHoverParticipant { + colorPicker = widget; + } + }; + for (const participant of this._participants) { if (groupedHoverParts.has(participant)) { const participantHoverParts = groupedHoverParts.get(participant)!; - this._renderDisposable.add(participant.renderHoverParts(participantHoverParts, fragment, statusBar)); + this._renderDisposable.add(participant.renderHoverParts(context, participantHoverParts)); } } if (statusBar.hasContent) { @@ -296,14 +298,11 @@ export class ContentHoverController extends Disposable implements IEditorHover { if (fragment.hasChildNodes()) { if (forceShowAtRange) { - this._widget.showAt(fragment, forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus); + this._widget.showAt(fragment, colorPicker, forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus); } else { - this._widget.showAt(fragment, new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus); + this._widget.showAt(fragment, colorPicker, new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus); } } - if (this._colorPicker) { - this._colorPicker.layout(); - } this._isChangingDecorations = true; this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, highlightRange ? [{ @@ -321,6 +320,7 @@ export class ContentHoverController extends Disposable implements IEditorHover { class ContentHoverVisibleData { constructor( + public readonly colorPicker: ColorPickerWidget | null, public readonly showAtPosition: Position | null, public readonly showAtRange: Range | null, public readonly preferAbove: boolean, @@ -345,6 +345,13 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { return this._visibleData?.showAtPosition ?? null; } + /** + * Returns `null` if the color picker is not visible. + */ + public get colorPicker(): ColorPickerWidget | null { + return this._visibleData?.colorPicker ?? null; + } + constructor( private readonly _editor: ICodeEditor, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -413,8 +420,8 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { codeClasses.forEach(node => this._editor.applyFontInfo(node)); } - public showAt(node: DocumentFragment, position: Position, range: Range | null, focus: boolean): void { - this._visibleData = new ContentHoverVisibleData(position, range, this._editor.getOption(EditorOption.hover).above, focus); + public showAt(node: DocumentFragment, colorPicker: ColorPickerWidget | null, position: Position, range: Range | null, focus: boolean): void { + this._visibleData = new ContentHoverVisibleData(colorPicker, position, range, this._editor.getOption(EditorOption.hover).above, focus); this._hoverVisibleKey.set(!!this._visibleData); this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); @@ -431,6 +438,9 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { if (focus) { this._hover.containerDomNode.focus(); } + if (colorPicker) { + colorPicker.layout(); + } } public hide(): void { diff --git a/src/vs/editor/contrib/hover/hoverOperation.ts b/src/vs/editor/contrib/hover/hoverOperation.ts index 420b941ca918d..6f293a94961f3 100644 --- a/src/vs/editor/contrib/hover/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/hoverOperation.ts @@ -7,29 +7,29 @@ import { AsyncIterableObject, CancelableAsyncIterableObject, createCancelableAsy import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; -export interface IHoverComputer { +export interface IHoverComputer { /** * This is called after half the hover time */ - computeAsync?: (token: CancellationToken) => AsyncIterableObject; + computeAsync?: (token: CancellationToken) => AsyncIterableObject; /** * This is called after all the hover time */ - computeSync?: () => Result[]; + computeSync?: () => T[]; /** * This is called whenever one of the compute* methods returns a truey value */ - onResult: (result: Result[], isFromSynchronousComputation: boolean) => void; + onResult: (result: T[], isFromSynchronousComputation: boolean) => void; /** * This is what will be sent as progress/complete to the computation promise */ - getResult: () => Result[]; + getResult: () => T[]; - getResultWithLoadingMessage: () => Result[]; + getResultWithLoadingMessage: () => T[]; } diff --git a/src/vs/editor/contrib/hover/hoverTypes.ts b/src/vs/editor/contrib/hover/hoverTypes.ts index 633a67dedc7ce..70f4abead78a3 100644 --- a/src/vs/editor/contrib/hover/hoverTypes.ts +++ b/src/vs/editor/contrib/hover/hoverTypes.ts @@ -33,7 +33,6 @@ export interface IHoverPart { export interface IEditorHover { hide(): void; onContentsChanged(): void; - setColorPicker(widget: ColorPickerWidget): void; } export const enum HoverAnchorType { @@ -83,10 +82,16 @@ export interface IEditorHoverAction { setEnabled(enabled: boolean): void; } +export interface IEditorHoverRenderContext { + readonly fragment: DocumentFragment; + readonly statusBar: IEditorHoverStatusBar; + setColorPicker(widget: ColorPickerWidget): void; +} + export interface IEditorHoverParticipant { suggestHoverAnchor?(mouseEvent: IEditorMouseEvent): HoverAnchor | null; computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): T[]; computeAsync?(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject; createLoadingMessage?(anchor: HoverAnchor): T | null; - renderHoverParts(hoverParts: T[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable; + renderHoverParts(context: IEditorHoverRenderContext, hoverParts: T[]): IDisposable; } diff --git a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts index 29a552299b2a9..61fb38d7312f7 100644 --- a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts @@ -17,7 +17,7 @@ import { IModelDecoration } from 'vs/editor/common/model'; import { HoverProviderRegistry } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/services/language'; import { getHover } from 'vs/editor/contrib/hover/getHover'; -import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -115,8 +115,8 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant fragment.appendChild(this.renderMarkerHover(msg, disposables))); + hoverParts.forEach(msg => context.fragment.appendChild(this.renderMarkerHover(msg, disposables))); const markerHoverForStatusbar = hoverParts.length === 1 ? hoverParts[0] : hoverParts.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))[0]; - this.renderMarkerStatusbar(markerHoverForStatusbar, statusBar, disposables); + this.renderMarkerStatusbar(markerHoverForStatusbar, context.statusBar, disposables); return disposables; } diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts index 074d8d174f5f5..88135b55e573b 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts @@ -12,7 +12,7 @@ import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/brows import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration } from 'vs/editor/common/model'; import { ILanguageService } from 'vs/editor/common/services/language'; -import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHover, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import { commitInlineSuggestionAction, GhostTextController, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction } from 'vs/editor/contrib/inlineCompletions/ghostTextController'; import * as nls from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -89,12 +89,12 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan return []; } - renderHoverParts(hoverParts: InlineCompletionsHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable { + renderHoverParts(context: IEditorHoverRenderContext, hoverParts: InlineCompletionsHover[]): IDisposable { const disposableStore = new DisposableStore(); const part = hoverParts[0]; if (this.accessibilityService.isScreenReaderOptimized()) { - this.renderScreenReaderText(part, fragment, disposableStore); + this.renderScreenReaderText(part, context.fragment, disposableStore); } const menu = disposableStore.add(this._menuService.createMenu( @@ -102,17 +102,17 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan this._contextKeyService )); - const previousAction = statusBar.addAction({ + const previousAction = context.statusBar.addAction({ label: nls.localize('showNextInlineSuggestion', "Next"), commandId: ShowNextInlineSuggestionAction.ID, run: () => this._commandService.executeCommand(ShowNextInlineSuggestionAction.ID) }); - const nextAction = statusBar.addAction({ + const nextAction = context.statusBar.addAction({ label: nls.localize('showPreviousInlineSuggestion', "Previous"), commandId: ShowPreviousInlineSuggestionAction.ID, run: () => this._commandService.executeCommand(ShowPreviousInlineSuggestionAction.ID) }); - statusBar.addAction({ + context.statusBar.addAction({ label: nls.localize('acceptInlineSuggestion', "Accept"), commandId: commitInlineSuggestionAction.id, run: () => this._commandService.executeCommand(commitInlineSuggestionAction.id) @@ -131,7 +131,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan for (const [_, group] of menu.getActions()) { for (const action of group) { if (action instanceof MenuItemAction) { - statusBar.addAction({ + context.statusBar.addAction({ label: action.label, commandId: action.item.id, run: () => this._commandService.executeCommand(action.item.id) diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index a1d0b41a2cd20..ad5e07fb3aee1 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -22,7 +22,7 @@ import { UnicodeHighlighterOptions, UnicodeHighlighterReason, UnicodeHighlighter import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { ILanguageService } from 'vs/editor/common/services/language'; import { isModelDecorationInComment, isModelDecorationInString, isModelDecorationVisible } from 'vs/editor/common/viewModel/viewModelDecorations'; -import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import { MarkdownHover, renderMarkdownHovers } from 'vs/editor/contrib/hover/markdownHoverParticipant'; import { BannerController } from 'vs/editor/contrib/unicodeHighlighter/bannerController'; import * as nls from 'vs/nls'; @@ -492,8 +492,8 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa return result; } - public renderHoverParts(hoverParts: MarkdownHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable { - return renderMarkdownHovers(hoverParts, fragment, this._editor, this._hover, this._languageService, this._openerService); + public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: MarkdownHover[]): IDisposable { + return renderMarkdownHovers(hoverParts, context.fragment, this._editor, this._hover, this._languageService, this._openerService); } } From e16700b28b5c32026d68821baf8cc8e5db5fce92 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Jan 2022 21:39:12 +0100 Subject: [PATCH 1104/2210] Avoid passing `ContentHoverController` to hover participants --- .../contrib/hover/colorHoverParticipant.ts | 5 ++-- src/vs/editor/contrib/hover/contentHover.ts | 26 ++++++++++--------- src/vs/editor/contrib/hover/hoverTypes.ts | 26 ++++++++++++++----- .../contrib/hover/markdownHoverParticipant.ts | 12 ++++----- .../contrib/hover/markerHoverParticipant.ts | 17 ++++++------ .../inlineCompletionsHoverParticipant.ts | 11 ++++---- .../unicodeHighlighter/unicodeHighlighter.ts | 5 ++-- 7 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/vs/editor/contrib/hover/colorHoverParticipant.ts b/src/vs/editor/contrib/hover/colorHoverParticipant.ts index d9654d8972999..c38ec304835d3 100644 --- a/src/vs/editor/contrib/hover/colorHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/colorHoverParticipant.ts @@ -16,7 +16,7 @@ import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color'; import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel'; import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget'; -import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import { IThemeService } from 'vs/platform/theme/common/themeService'; export class ColorHover implements IHoverPart { @@ -47,7 +47,6 @@ export class ColorHoverParticipant implements IEditorHoverParticipant { colorPicker = widget; + }, + onContentsChanged: (): void => { + this._widget.onContentsChanged(); + }, + hide: (): void => { + this.hide(); } }; diff --git a/src/vs/editor/contrib/hover/hoverTypes.ts b/src/vs/editor/contrib/hover/hoverTypes.ts index 70f4abead78a3..71ec3220408cd 100644 --- a/src/vs/editor/contrib/hover/hoverTypes.ts +++ b/src/vs/editor/contrib/hover/hoverTypes.ts @@ -26,15 +26,12 @@ export interface IHoverPart { * even in the case of multiple hover parts. */ readonly forceShowAtRange?: boolean; - + /** + * Is this hover part still valid for this new anchor? + */ isValidForHoverAnchor(anchor: HoverAnchor): boolean; } -export interface IEditorHover { - hide(): void; - onContentsChanged(): void; -} - export const enum HoverAnchorType { Range = 1, ForeignElement = 2 @@ -83,9 +80,26 @@ export interface IEditorHoverAction { } export interface IEditorHoverRenderContext { + /** + * The fragment where dom elements should be attached. + */ readonly fragment: DocumentFragment; + /** + * The status bar for actions for this hover. + */ readonly statusBar: IEditorHoverStatusBar; + /** + * Set if the hover will render a color picker widget. + */ setColorPicker(widget: ColorPickerWidget): void; + /** + * The contents rendered inside the fragment have been changed, which means that the hover should relayout. + */ + onContentsChanged(): void; + /** + * Hide the hover. + */ + hide(): void; } export interface IEditorHoverParticipant { diff --git a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts index 61fb38d7312f7..c1dc56ffdab3b 100644 --- a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts @@ -17,7 +17,7 @@ import { IModelDecoration } from 'vs/editor/common/model'; import { HoverProviderRegistry } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/services/language'; import { getHover } from 'vs/editor/contrib/hover/getHover'; -import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -46,7 +46,6 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant { hoverContentsElement.className = 'hover-contents code-hover-contents'; - hover.onContentsChanged(); + context.onContentsChanged(); })); const renderedContents = disposables.add(renderer.render(contents)); hoverContentsElement.appendChild(renderedContents.element); - fragment.appendChild(markdownHoverElement); + context.fragment.appendChild(markdownHoverElement); } } return disposables; diff --git a/src/vs/editor/contrib/hover/markerHoverParticipant.ts b/src/vs/editor/contrib/hover/markerHoverParticipant.ts index 4901ee327343d..044017037c1a0 100644 --- a/src/vs/editor/contrib/hover/markerHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markerHoverParticipant.ts @@ -19,7 +19,7 @@ import { CodeActionSet, getCodeActions } from 'vs/editor/contrib/codeAction/code import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; import { CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/gotoError'; -import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverRenderContext, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import * as nls from 'vs/nls'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IMarker, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; @@ -58,7 +58,6 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant context.fragment.appendChild(this.renderMarkerHover(msg, disposables))); const markerHoverForStatusbar = hoverParts.length === 1 ? hoverParts[0] : hoverParts.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))[0]; - this.renderMarkerStatusbar(markerHoverForStatusbar, context.statusBar, disposables); + this.renderMarkerStatusbar(context, markerHoverForStatusbar, disposables); return disposables; } @@ -166,13 +165,13 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant { - this._hover.hide(); + context.hide(); MarkerController.get(this._editor)?.showAtMarker(markerHover.marker); this._editor.focus(); } @@ -180,7 +179,7 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant { @@ -224,7 +223,7 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant { constructor( private readonly _editor: ICodeEditor, - private readonly _hover: IEditorHover, @ICommandService private readonly _commandService: ICommandService, @IMenuService private readonly _menuService: IMenuService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -94,7 +93,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan const part = hoverParts[0]; if (this.accessibilityService.isScreenReaderOptimized()) { - this.renderScreenReaderText(part, context.fragment, disposableStore); + this.renderScreenReaderText(context, part, disposableStore); } const menu = disposableStore.add(this._menuService.createMenu( @@ -143,7 +142,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan return disposableStore; } - private renderScreenReaderText(part: InlineCompletionsHover, fragment: DocumentFragment, disposableStore: DisposableStore) { + private renderScreenReaderText(context: IEditorHoverRenderContext, part: InlineCompletionsHover, disposableStore: DisposableStore) { const $ = dom.$; const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); @@ -151,7 +150,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan const render = (code: string) => { disposableStore.add(renderer.onDidRenderAsync(() => { hoverContentsElement.className = 'hover-contents code-hover-contents'; - this._hover.onContentsChanged(); + context.onContentsChanged(); })); const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Suggestion:"); @@ -164,6 +163,6 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan const lineText = this._editor.getModel()!.getLineContent(ghostText.lineNumber); render(ghostText.renderForScreenReader(lineText)); } - fragment.appendChild(markdownHoverElement); + context.fragment.appendChild(markdownHoverElement); } } diff --git a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts index ad5e07fb3aee1..5ec996aa2136f 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts @@ -22,7 +22,7 @@ import { UnicodeHighlighterOptions, UnicodeHighlighterReason, UnicodeHighlighter import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { ILanguageService } from 'vs/editor/common/services/language'; import { isModelDecorationInComment, isModelDecorationInString, isModelDecorationVisible } from 'vs/editor/common/viewModel/viewModelDecorations'; -import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes'; import { MarkdownHover, renderMarkdownHovers } from 'vs/editor/contrib/hover/markdownHoverParticipant'; import { BannerController } from 'vs/editor/contrib/unicodeHighlighter/bannerController'; import * as nls from 'vs/nls'; @@ -414,7 +414,6 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa constructor( private readonly _editor: ICodeEditor, - private readonly _hover: IEditorHover, @ILanguageService private readonly _languageService: ILanguageService, @IOpenerService private readonly _openerService: IOpenerService, ) { @@ -493,7 +492,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa } public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: MarkdownHover[]): IDisposable { - return renderMarkdownHovers(hoverParts, context.fragment, this._editor, this._hover, this._languageService, this._openerService); + return renderMarkdownHovers(context, hoverParts, this._editor, this._languageService, this._openerService); } } From d56cb39ab12d667423a34e6cd869781fda4a9c18 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Jan 2022 22:06:14 +0100 Subject: [PATCH 1105/2210] Simplifications --- src/vs/editor/contrib/hover/contentHover.ts | 67 ++++++++++----------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index 2cac23e2cd53d..a3d5b3183ed9b 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -43,7 +43,6 @@ export class ContentHoverController extends Disposable { private _messages: IHoverPart[]; private _messagesAreComplete: boolean; - private _lastAnchor: HoverAnchor | null; private readonly _computer: ContentHoverComputer; private readonly _hoverOperation: HoverOperation; private _highlightDecorations: string[]; @@ -70,7 +69,6 @@ export class ContentHoverController extends Disposable { this._messages = []; this._messagesAreComplete = false; - this._lastAnchor = null; this._computer = new ContentHoverComputer(this._editor, this._participants); this._highlightDecorations = []; this._isChangingDecorations = false; @@ -95,9 +93,9 @@ export class ContentHoverController extends Disposable { } })); this._register(TokenizationRegistry.onDidChange(() => { - if (this._widget.position && this._lastAnchor && this._messages.length > 0) { + if (this._widget.position && this._computer.anchor && this._messages.length > 0) { this._widget.clear(); - this._renderMessages(this._lastAnchor, this._messages); + this._renderMessages(this._computer.anchor, this._messages); } })); } @@ -181,7 +179,7 @@ export class ContentHoverController extends Disposable { } private _startShowingAt(anchor: HoverAnchor, mode: HoverStartMode, focus: boolean): void { - if (this._lastAnchor && this._lastAnchor.equals(anchor)) { + if (this._computer.anchor && this._computer.anchor.equals(anchor)) { // We have to show the widget at the exact same range as before, so no work is needed return; } @@ -192,7 +190,7 @@ export class ContentHoverController extends Disposable { // The range might have changed, but the hover is visible // Instead of hiding it completely, filter out messages that are still in the new range and // kick off a new computation - if (!this._lastAnchor || !anchor.canAdoptVisibleHover(this._lastAnchor, this._widget.position)) { + if (!this._computer.anchor || !anchor.canAdoptVisibleHover(this._computer.anchor, this._widget.position)) { this.hide(); } else { const filteredMessages = this._messages.filter((m) => m.isValidForHoverAnchor(anchor)); @@ -207,14 +205,13 @@ export class ContentHoverController extends Disposable { } } - this._lastAnchor = anchor; - this._computer.setAnchor(anchor); + this._computer.anchor = anchor; this._shouldFocus = focus; this._hoverOperation.start(mode); } public hide(): void { - this._lastAnchor = null; + this._computer.anchor = null; this._hoverOperation.cancel(); this._widget.hide(); @@ -236,8 +233,8 @@ export class ContentHoverController extends Disposable { this._messages = result; this._messagesAreComplete = complete; - if (this._lastAnchor && this._messages.length > 0) { - this._renderMessages(this._lastAnchor, this._messages); + if (this._computer.anchor && this._messages.length > 0) { + this._renderMessages(this._computer.anchor, this._messages); } else if (complete) { this.hide(); } @@ -335,6 +332,7 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { static readonly ID = 'editor.contrib.contentHoverWidget'; public readonly allowEditorOverflow = true; + private readonly _hoverVisibleKey = EditorContextKeys.hoverVisible.bindTo(this._contextKeyService); private readonly _hover: HoverWidget = this._register(new HoverWidget()); @@ -360,9 +358,6 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { ) { super(); - this._hoverVisibleKey.set(!!this._visibleData); - this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); - this._register(this._editor.onDidLayoutChange(() => this._layout())); this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (e.hasChanged(EditorOption.fontInfo)) { @@ -370,6 +365,7 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { } })); + this._setVisibleData(null); this._layout(); this._editor.addContentWidget(this); } @@ -407,6 +403,12 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { }; } + private _setVisibleData(visibleData: ContentHoverVisibleData | null): void { + this._visibleData = visibleData; + this._hoverVisibleKey.set(!!this._visibleData); + this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); + } + private _layout(): void { const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); const { fontSize, lineHeight } = this._editor.getOption(EditorOption.fontInfo); @@ -423,9 +425,7 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { } public showAt(node: DocumentFragment, colorPicker: ColorPickerWidget | null, position: Position, range: Range | null, focus: boolean): void { - this._visibleData = new ContentHoverVisibleData(colorPicker, position, range, this._editor.getOption(EditorOption.hover).above, focus); - this._hoverVisibleKey.set(!!this._visibleData); - this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); + this._setVisibleData(new ContentHoverVisibleData(colorPicker, position, range, this._editor.getOption(EditorOption.hover).above, focus)); this._hover.contentsDomNode.textContent = ''; this._hover.contentsDomNode.appendChild(node); @@ -448,10 +448,7 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { public hide(): void { if (this._visibleData) { const stoleFocus = this._visibleData.stoleFocus; - this._visibleData = null; - this._hoverVisibleKey.set(!!this._visibleData); - this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); - + this._setVisibleData(null); this._editor.layoutContentWidget(this); if (stoleFocus) { this._editor.focus(); @@ -506,6 +503,15 @@ class ContentHoverComputer implements IHoverComputer { private _result: IHoverPart[]; private _anchor: HoverAnchor | null; + public get anchor(): HoverAnchor | null { + return this._anchor; + } + + public set anchor(value: HoverAnchor | null) { + this._anchor = value; + this._result = []; + } + constructor( editor: ICodeEditor, private readonly _participants: readonly IEditorHoverParticipant[] @@ -515,11 +521,6 @@ class ContentHoverComputer implements IHoverComputer { this._anchor = null; } - public setAnchor(anchor: HoverAnchor): void { - this._anchor = anchor; - this._result = []; - } - public clearResult(): void { this._result = []; } @@ -563,17 +564,15 @@ class ContentHoverComputer implements IHoverComputer { const lineDecorations = ContentHoverComputer._getLineDecorations(this._editor, anchor); return AsyncIterableObject.merge( - this._participants.map(participant => this._computeAsync(participant, lineDecorations, anchor, token)) + this._participants.map((participant) => { + if (!participant.computeAsync) { + return AsyncIterableObject.EMPTY; + } + return participant.computeAsync(anchor, lineDecorations, token); + }) ); } - private _computeAsync(participant: IEditorHoverParticipant, lineDecorations: IModelDecoration[], anchor: HoverAnchor, token: CancellationToken): AsyncIterableObject { - if (!participant.computeAsync) { - return AsyncIterableObject.EMPTY; - } - return participant.computeAsync(anchor, lineDecorations, token); - } - public computeSync(): IHoverPart[] { if (!this._editor.hasModel() || !this._anchor) { return []; From ea2afbaf90cb6d9672d8a681e90492d23c687064 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 7 Jan 2022 07:40:20 +0100 Subject: [PATCH 1106/2210] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 52c0b0f3ad2f5..be6ce2e0f5738 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.64.0", - "distro": "553bb9d3803948fcccf1a35374942d8aba9c9c73", + "distro": "de857411bebe2132093f797a89437dd2b8199c10", "author": { "name": "Microsoft Corporation" }, From 3f8abf0de4be6cdf83abf120d463d6814735afab Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 7 Jan 2022 09:39:52 +0100 Subject: [PATCH 1107/2210] resolve item only once, https://github.com/microsoft/vscode/issues/129528 --- .../editor/contrib/inlayHints/inlayHints.ts | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/inlayHints.ts b/src/vs/editor/contrib/inlayHints/inlayHints.ts index 241950b5f5447..da6e8fa4b283f 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHints.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHints.ts @@ -21,24 +21,22 @@ export class InlayHintItem { readonly resolve: (token: CancellationToken) => Promise; constructor(readonly hint: InlayHint, readonly anchor: InlayHintAnchor, provider: InlayHintsProvider) { + let resolve: Promise | undefined; if (!provider.resolveInlayHint) { - this.resolve = async () => { }; - } else { - let isResolved = false; - this.resolve = async token => { - if (isResolved) { - return; - } - try { - const newHint = await provider.resolveInlayHint!(this.hint, token); + resolve = Promise.resolve(); + } + this.resolve = async token => { + if (!resolve) { + resolve = Promise.resolve(provider.resolveInlayHint!(this.hint, token)).then(newHint => { this.hint.tooltip = newHint?.tooltip ?? this.hint.tooltip; this.hint.label = newHint?.label ?? this.hint.label; - isResolved = true; - } catch (err) { + }).catch(err => { onUnexpectedExternalError(err); - } - }; - } + resolve = undefined; + }); + } + return resolve; + }; } } From 6540fc6105fe6d02772f9d371ddbf8d607ccb254 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 7 Jan 2022 09:54:38 +0100 Subject: [PATCH 1108/2210] more inlay hint range tweaks,https://github.com/microsoft/vscode/issues/129528 --- src/vs/editor/contrib/inlayHints/inlayHints.ts | 12 ++++++++---- .../contrib/inlayHints/inlayHintsController.ts | 14 ++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/inlayHints.ts b/src/vs/editor/contrib/inlayHints/inlayHints.ts index da6e8fa4b283f..9b7010f96883f 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHints.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHints.ts @@ -13,21 +13,21 @@ import { InlayHint, InlayHintList, InlayHintsProvider, InlayHintsProviderRegistr import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; export class InlayHintAnchor { - constructor(public range: Range, readonly direction: 'before' | 'after', readonly usesWordRange: boolean) { } + constructor(readonly range: Range, readonly direction: 'before' | 'after', readonly usesWordRange: boolean) { } } export class InlayHintItem { readonly resolve: (token: CancellationToken) => Promise; - constructor(readonly hint: InlayHint, readonly anchor: InlayHintAnchor, provider: InlayHintsProvider) { + constructor(readonly hint: InlayHint, readonly anchor: InlayHintAnchor, private readonly _provider: InlayHintsProvider) { let resolve: Promise | undefined; - if (!provider.resolveInlayHint) { + if (!_provider.resolveInlayHint) { resolve = Promise.resolve(); } this.resolve = async token => { if (!resolve) { - resolve = Promise.resolve(provider.resolveInlayHint!(this.hint, token)).then(newHint => { + resolve = Promise.resolve(_provider.resolveInlayHint!(this.hint, token)).then(newHint => { this.hint.tooltip = newHint?.tooltip ?? this.hint.tooltip; this.hint.label = newHint?.label ?? this.hint.label; }).catch(err => { @@ -38,6 +38,10 @@ export class InlayHintItem { return resolve; }; } + + with(delta: { anchor: InlayHintAnchor }): InlayHintItem { + return new InlayHintItem(this.hint, delta.anchor, this._provider); + } } export class InlayHintsFragments { diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 89acb885df851..b712330af11dd 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -23,7 +23,7 @@ import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, TrackedRangeSti import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ClickLinkGesture } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; -import { InlayHintItem, InlayHintsFragments } from 'vs/editor/contrib/inlayHints/inlayHints'; +import { InlayHintAnchor, InlayHintItem, InlayHintsFragments } from 'vs/editor/contrib/inlayHints/inlayHints'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import * as colors from 'vs/platform/theme/common/colorRegistry'; @@ -163,7 +163,7 @@ export class InlayHintsController implements IEditorContribution { const range = new Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber)); const lineHints = new Set(); for (let data of this._decorationsMetadata.values()) { - if (range.containsPosition(data.item.hint.position)) { + if (range.containsRange(data.item.anchor.range)) { lineHints.add(data.item); } } @@ -188,21 +188,23 @@ export class InlayHintsController implements IEditorContribution { } private _cacheHintsForFastRestore(model: ITextModel): void { - const items = new Set(); + const items = new Map(); for (const [id, obj] of this._decorationsMetadata) { if (items.has(obj.item)) { // an inlay item can be rendered as multiple decorations // but they will all uses the same range continue; } - items.add(obj.item); + let value = obj.item; const range = model.getDecorationRange(id); if (range) { // update range with whatever the editor has tweaked it to - obj.item.anchor.range = range; + const anchor = new InlayHintAnchor(range, obj.item.anchor.direction, obj.item.anchor.usesWordRange); + value = obj.item.with({ anchor }); } + items.set(obj.item, value); } - this._cache.set(model, Array.from(items)); + this._cache.set(model, Array.from(items.values())); } private _getHintsRanges(): Range[] { From a4d7014d7ae9efce4ad597bc3315e184d25c2a05 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 7 Jan 2022 10:18:53 +0100 Subject: [PATCH 1109/2210] Git - add setting to control repository scan depth (#140260) --- extensions/git/package.json | 6 +++++ extensions/git/package.nls.json | 1 + extensions/git/src/model.ts | 46 +++++++++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 05dddd0d2a78d..da96c49e3a740 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2215,6 +2215,12 @@ "scope": "resource", "default": 10000, "description": "%config.statusLimit%" + }, + "git.repositoryScanMaxDepth": { + "type": "number", + "scope": "resource", + "default": 1, + "markdownDescription": "%config.repositoryScanMaxDepth%" } } }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 7b3af1f5ca9c2..481b1cb09c0f5 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -193,6 +193,7 @@ "config.showUnpublishedCommitsButton.whenEmpty": "Only shows the action button if there are no other changes and there are unpublished commits.", "config.showUnpublishedCommitsButton.never": "Never shows the action button.", "config.statusLimit": "Controls how to limit the number of changes that can be parsed from Git status command. Can be set to 0 for no limit.", + "config.repositoryScanMaxDepth": "Controls the depth used when scanning workspace folders for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`. Can be set to `-1` for no limit.", "submenu.explorer": "Git", "submenu.commit": "Commit", "submenu.commit.amend": "Amend", diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 60a231321b1da..a9a7b0e5f31a4 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -17,6 +17,7 @@ import { Askpass } from './askpass'; import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; +import { Log, LogLevel } from './log'; const localize = nls.loadMessageBundle(); @@ -133,12 +134,20 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR } /** - * Scans the first level of each workspace folder, looking - * for git repositories. + * Scans each workspace folder, looking for git repositories. By + * default it scans one level deep but that can be changed using + * the git.repositoryScanMaxDepth setting. */ private async scanWorkspaceFolders(): Promise { const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); + const repositoryScanMaxDepth = config.get('repositoryScanMaxDepth', 1); + + // Log repository scan settings + if (Log.logLevel <= LogLevel.Trace) { + this.outputChannel.appendLine(`${logTimestamp()} Trace: autoRepositoryDetection="${autoRepositoryDetection}"`); + this.outputChannel.appendLine(`${logTimestamp()} Trace: repositoryScanMaxDepth=${repositoryScanMaxDepth}`); + } if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') { return; @@ -146,9 +155,11 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR await Promise.all((workspace.workspaceFolders || []).map(async folder => { const root = folder.uri.fsPath; - const children = (await fs.promises.readdir(root, { withFileTypes: true })).filter(dirent => dirent.isDirectory()).map(dirent => dirent.name); - const subfolders = new Set(children.filter(child => child !== '.git').map(child => path.join(root, child))); + // Workspace folder children + const subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth)); + + // Repository scan folders const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || []; for (const scanPath of scanPaths) { if (scanPath === '.git') { @@ -167,6 +178,29 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR })); } + private async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number): Promise { + const result: string[] = []; + const foldersToTravers = [{ path: workspaceFolder, depth: 0 }]; + + while (foldersToTravers.length > 0) { + const currentFolder = foldersToTravers.shift()!; + + if (currentFolder.depth < maxDepth || maxDepth === -1) { + const children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true }); + const childrenFolders = children + .filter(dirent => dirent.isDirectory() && dirent.name !== '.git') + .map(dirent => path.join(currentFolder.path, dirent.name)); + + result.push(...childrenFolders); + foldersToTravers.push(...childrenFolders.map(folder => { + return { path: folder, depth: currentFolder.depth + 1 }; + })); + } + } + + return result; + } + private onPossibleGitRepositoryChange(uri: Uri): void { const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); @@ -303,7 +337,9 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR repository.status(); // do not await this, we want SCM to know about the repo asap } catch (ex) { // noop - this.outputChannel.appendLine(`${logTimestamp()} Opening repository for path='${repoPath}' failed; ex=${ex}`); + if (Log.logLevel <= LogLevel.Trace) { + this.outputChannel.appendLine(`${logTimestamp()} Trace: Opening repository for path='${repoPath}' failed; ex=${ex}`); + } } } From 37f93d8273c99e781581f0940272214951f11e0b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 7 Jan 2022 08:31:34 +0100 Subject: [PATCH 1110/2210] Improvements to `HoverOperation` --- src/vs/editor/contrib/hover/contentHover.ts | 19 +--- src/vs/editor/contrib/hover/hoverOperation.ts | 95 ++++++++++--------- src/vs/editor/contrib/hover/marginHover.ts | 12 +-- 3 files changed, 60 insertions(+), 66 deletions(-) diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index a3d5b3183ed9b..46bbc67058af6 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -75,18 +75,12 @@ export class ContentHoverController extends Disposable { this._shouldFocus = false; this._renderDisposable = null; - this._hoverOperation = new HoverOperation( - this._computer, - result => this._withResult(result, true), - null, - result => this._withResult(result, false), - this._editor.getOption(EditorOption.hover).delay - ); + this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer)); + this._register(this._hoverOperation.onResult((result) => { + this._withResult(result.value, result.isComplete); + })); this._register(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); - this._register(this._editor.onDidChangeConfiguration(() => { - this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay); - })); this._register(dom.addStandardDisposableListener(this._widget.getDomNode(), 'keydown', (e) => { if (e.equals(KeyCode.Escape)) { this.hide(); @@ -100,11 +94,6 @@ export class ContentHoverController extends Disposable { })); } - public override dispose(): void { - this._hoverOperation.cancel(); - super.dispose(); - } - private _shouldShowAt(mouseEvent: IEditorMouseEvent): boolean { const targetType = mouseEvent.target.type; if (targetType === MouseTargetType.CONTENT_TEXT) { diff --git a/src/vs/editor/contrib/hover/hoverOperation.ts b/src/vs/editor/contrib/hover/hoverOperation.ts index 6f293a94961f3..a736483e73742 100644 --- a/src/vs/editor/contrib/hover/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/hoverOperation.ts @@ -6,6 +6,10 @@ import { AsyncIterableObject, CancelableAsyncIterableObject, createCancelableAsyncIterable, RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; export interface IHoverComputer { @@ -46,41 +50,54 @@ export const enum HoverStartMode { Immediate = 1 } -export class HoverOperation { - - private readonly _computer: IHoverComputer; - private _state: ComputeHoverOperationState; - private _hoverTime: number; - - private readonly _firstWaitScheduler: RunOnceScheduler; - private readonly _secondWaitScheduler: RunOnceScheduler; - private readonly _loadingMessageScheduler: RunOnceScheduler; - private _asyncIterable: CancelableAsyncIterableObject | null; - private _asyncIterableDone: boolean; - - private readonly _completeCallback: (r: Result[]) => void; - private readonly _errorCallback: ((err: any) => void) | null | undefined; - private readonly _progressCallback: (progress: any) => void; - - constructor(computer: IHoverComputer, success: (r: Result[]) => void, error: ((err: any) => void) | null | undefined, progress: (progress: any) => void, hoverTime: number) { - this._computer = computer; - this._state = ComputeHoverOperationState.IDLE; - this._hoverTime = hoverTime; - - this._firstWaitScheduler = new RunOnceScheduler(() => this._triggerAsyncComputation(), 0); - this._secondWaitScheduler = new RunOnceScheduler(() => this._triggerSyncComputation(), 0); - this._loadingMessageScheduler = new RunOnceScheduler(() => this._showLoadingMessage(), 0); +export class HoverResult { + constructor( + public readonly value: T[], + public readonly isComplete: boolean, + public readonly hasLoadingMessage: boolean, + ) { } +} - this._asyncIterable = null; - this._asyncIterableDone = false; +/** + * Computing the hover is very fine tuned. + * + * Suppose the hover delay is 300ms (the default). Then, when resting the mouse at an anchor: + * - at 150ms, the async computation is triggered (i.e. semantic hover) + * - if async results already come in, they are not rendered yet. + * - at 300ms, the sync computation is triggered (i.e. decorations, markers) + * - if there are sync or async results, they are rendered. + * - at 900ms, if the async computation hasn't finished, a "Loading..." result is added. + */ +export class HoverOperation extends Disposable { + + private readonly _onResult = this._register(new Emitter>()); + public readonly onResult = this._onResult.event; + + private readonly _firstWaitScheduler = this._register(new RunOnceScheduler(() => this._triggerAsyncComputation(), 0)); + private readonly _secondWaitScheduler = this._register(new RunOnceScheduler(() => this._triggerSyncComputation(), 0)); + private readonly _loadingMessageScheduler = this._register(new RunOnceScheduler(() => this._showLoadingMessage(), 0)); + + private _state = ComputeHoverOperationState.IDLE; + private _asyncIterable: CancelableAsyncIterableObject | null = null; + private _asyncIterableDone: boolean = false; + + constructor( + private readonly _editor: ICodeEditor, + private readonly _computer: IHoverComputer + ) { + super(); + } - this._completeCallback = success; - this._errorCallback = error; - this._progressCallback = progress; + public override dispose(): void { + if (this._asyncIterable) { + this._asyncIterable.cancel(); + this._asyncIterable = null; + } + super.dispose(); } - public setHoverTime(hoverTime: number): void { - this._hoverTime = hoverTime; + private get _hoverTime(): number { + return this._editor.getOption(EditorOption.hover).delay; } private _firstWaitTime(): number { @@ -114,7 +131,7 @@ export class HoverOperation { this._asyncIterableDone = true; this._withAsyncResult(); } catch (e) { - this._onError(e); + onUnexpectedError(e); } })(); @@ -152,22 +169,14 @@ export class HoverOperation { } private _onComplete(): void { - this._completeCallback(this._computer.getResult()); - } - - private _onError(error: any): void { - if (this._errorCallback) { - this._errorCallback(error); - } else { - onUnexpectedError(error); - } + this._onResult.fire(new HoverResult(this._computer.getResult(), true, false)); } private _onProgress(): void { if (this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION_SHOWING_LOADING) { - this._progressCallback(this._computer.getResultWithLoadingMessage()); + this._onResult.fire(new HoverResult(this._computer.getResultWithLoadingMessage(), false, true)); } else { - this._progressCallback(this._computer.getResult()); + this._onResult.fire(new HoverResult(this._computer.getResult(), false, false)); } } diff --git a/src/vs/editor/contrib/hover/marginHover.ts b/src/vs/editor/contrib/hover/marginHover.ts index 3c83a7196b012..f0cb4ad3eb68f 100644 --- a/src/vs/editor/contrib/hover/marginHover.ts +++ b/src/vs/editor/contrib/hover/marginHover.ts @@ -120,13 +120,10 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { this._markdownRenderer = this._register(new MarkdownRenderer({ editor: this._editor }, languageService, openerService)); this._computer = new MarginHoverComputer(this._editor); - this._hoverOperation = new HoverOperation( - this._computer, - (result: IHoverMessage[]) => this._withResult(result), - undefined, - (result: any) => this._withResult(result), - 300 - ); + this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer)); + this._register(this._hoverOperation.onResult((result) => { + this._withResult(result.value); + })); this._register(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { @@ -139,7 +136,6 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { } public override dispose(): void { - this._hoverOperation.cancel(); this._editor.removeOverlayWidget(this); super.dispose(); } From 23b4817e0e10b23127945a16bd80be589cd2a89b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 7 Jan 2022 09:09:04 +0100 Subject: [PATCH 1111/2210] Let `HoverOperation` manage the result --- src/vs/editor/contrib/hover/contentHover.ts | 11 +- src/vs/editor/contrib/hover/hoverOperation.ts | 16 ++- src/vs/editor/contrib/hover/marginHover.ts | 131 +++++++++--------- 3 files changed, 80 insertions(+), 78 deletions(-) diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index 46bbc67058af6..8fff435e4e5a8 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -155,7 +155,6 @@ export class ContentHoverController extends Disposable { // The decorations have changed and the hover is visible, // we need to recompute the displayed text this._hoverOperation.cancel(); - this._computer.clearResult(); if (!this._widget.colorPicker) { // TODO@Michel ensure that displayed text for other decorations is computed even if color picker is in place this._hoverOperation.start(HoverStartMode.Delayed); @@ -498,7 +497,6 @@ class ContentHoverComputer implements IHoverComputer { public set anchor(value: HoverAnchor | null) { this._anchor = value; - this._result = []; } constructor( @@ -577,13 +575,8 @@ class ContentHoverComputer implements IHoverComputer { return coalesce(result); } - public onResult(result: IHoverPart[], isFromSynchronousComputation: boolean): void { - // Always put synchronous messages before asynchronous ones - if (isFromSynchronousComputation) { - this._result = result.concat(this._result); - } else { - this._result = this._result.concat(result); - } + public onResult(result: IHoverPart[]): void { + this._result = this._result.concat(result); } public getResult(): IHoverPart[] { diff --git a/src/vs/editor/contrib/hover/hoverOperation.ts b/src/vs/editor/contrib/hover/hoverOperation.ts index a736483e73742..7a00f6ebdbbaa 100644 --- a/src/vs/editor/contrib/hover/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/hoverOperation.ts @@ -23,10 +23,15 @@ export interface IHoverComputer { */ computeSync?: () => T[]; + /** + * Clear the stored result. + */ + clearResult(): void; + /** * This is called whenever one of the compute* methods returns a truey value */ - onResult: (result: T[], isFromSynchronousComputation: boolean) => void; + onResult: (result: T[]) => void; /** * This is what will be sent as progress/complete to the computation promise @@ -96,6 +101,10 @@ export class HoverOperation extends Disposable { super.dispose(); } + public clearResult(): void { + this._computer.clearResult(); + } + private get _hoverTime(): number { return this._editor.getOption(EditorOption.hover).delay; } @@ -124,7 +133,7 @@ export class HoverOperation extends Disposable { try { for await (const item of this._asyncIterable!) { if (item) { - this._computer.onResult([item], false); + this._computer.onResult([item]); this._onProgress(); } } @@ -142,7 +151,7 @@ export class HoverOperation extends Disposable { private _triggerSyncComputation(): void { if (this._computer.computeSync) { - this._computer.onResult(this._computer.computeSync(), true); + this._computer.onResult(this._computer.computeSync()); } if (this._asyncIterableDone) { @@ -203,6 +212,7 @@ export class HoverOperation extends Disposable { } public cancel(): void { + this.clearResult(); this._firstWaitScheduler.cancel(); this._secondWaitScheduler.cancel(); this._loadingMessageScheduler.cancel(); diff --git a/src/vs/editor/contrib/hover/marginHover.ts b/src/vs/editor/contrib/hover/marginHover.ts index f0cb4ad3eb68f..85e9abdc8a954 100644 --- a/src/vs/editor/contrib/hover/marginHover.ts +++ b/src/vs/editor/contrib/hover/marginHover.ts @@ -22,71 +22,6 @@ export interface IHoverMessage { value: IMarkdownString; } -class MarginHoverComputer implements IHoverComputer { - - private readonly _editor: ICodeEditor; - private _lineNumber: number; - private _result: IHoverMessage[]; - - constructor(editor: ICodeEditor) { - this._editor = editor; - this._lineNumber = -1; - this._result = []; - } - - public setLineNumber(lineNumber: number): void { - this._lineNumber = lineNumber; - this._result = []; - } - - public clearResult(): void { - this._result = []; - } - - public computeSync(): IHoverMessage[] { - - const toHoverMessage = (contents: IMarkdownString): IHoverMessage => { - return { - value: contents - }; - }; - - const lineDecorations = this._editor.getLineDecorations(this._lineNumber); - - const result: IHoverMessage[] = []; - if (!lineDecorations) { - return result; - } - - for (const d of lineDecorations) { - if (!d.options.glyphMarginClassName) { - continue; - } - - const hoverMessage = d.options.glyphMarginHoverMessage; - if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) { - continue; - } - - result.push(...asArray(hoverMessage).map(toHoverMessage)); - } - - return result; - } - - public onResult(result: IHoverMessage[], isFromSynchronousComputation: boolean): void { - this._result = this._result.concat(result); - } - - public getResult(): IHoverMessage[] { - return this._result; - } - - public getResultWithLoadingMessage(): IHoverMessage[] { - return this.getResult(); - } -} - export class MarginHoverWidget extends Widget implements IOverlayWidget { public static readonly ID = 'editor.contrib.modesGlyphHoverWidget'; @@ -162,7 +97,6 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { // The decorations have changed and the hover is visible, // we need to recompute the displayed text this._hoverOperation.cancel(); - this._computer.clearResult(); this._hoverOperation.start(HoverStartMode.Delayed); } } @@ -242,3 +176,68 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { this._hover.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`; } } + +class MarginHoverComputer implements IHoverComputer { + + private readonly _editor: ICodeEditor; + private _lineNumber: number; + private _result: IHoverMessage[]; + + constructor(editor: ICodeEditor) { + this._editor = editor; + this._lineNumber = -1; + this._result = []; + } + + public setLineNumber(lineNumber: number): void { + this._lineNumber = lineNumber; + this._result = []; + } + + public clearResult(): void { + this._result = []; + } + + public computeSync(): IHoverMessage[] { + + const toHoverMessage = (contents: IMarkdownString): IHoverMessage => { + return { + value: contents + }; + }; + + const lineDecorations = this._editor.getLineDecorations(this._lineNumber); + + const result: IHoverMessage[] = []; + if (!lineDecorations) { + return result; + } + + for (const d of lineDecorations) { + if (!d.options.glyphMarginClassName) { + continue; + } + + const hoverMessage = d.options.glyphMarginHoverMessage; + if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) { + continue; + } + + result.push(...asArray(hoverMessage).map(toHoverMessage)); + } + + return result; + } + + public onResult(result: IHoverMessage[]): void { + this._result = this._result.concat(result); + } + + public getResult(): IHoverMessage[] { + return this._result; + } + + public getResultWithLoadingMessage(): IHoverMessage[] { + return this.getResult(); + } +} From 8b79fd1ef329164801cfb38fc24198d149030bb6 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 7 Jan 2022 09:23:23 +0100 Subject: [PATCH 1112/2210] Store results in `HoverOperation` --- src/vs/editor/contrib/hover/contentHover.ts | 52 +++++++------------ src/vs/editor/contrib/hover/hoverOperation.ts | 37 +++---------- src/vs/editor/contrib/hover/marginHover.ts | 48 +++++------------ 3 files changed, 39 insertions(+), 98 deletions(-) diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index 8fff435e4e5a8..2bbfa53979baf 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -77,7 +77,8 @@ export class ContentHoverController extends Disposable { this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer)); this._register(this._hoverOperation.onResult((result) => { - this._withResult(result.value, result.isComplete); + const actualResult = (result.hasLoadingMessage ? this._addLoadingMessage(result.value) : result.value); + this._withResult(actualResult, result.isComplete); })); this._register(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); @@ -94,6 +95,20 @@ export class ContentHoverController extends Disposable { })); } + private _addLoadingMessage(result: IHoverPart[]): IHoverPart[] { + if (this._computer.anchor) { + for (const participant of this._participants) { + if (participant.createLoadingMessage) { + const loadingMessage = participant.createLoadingMessage(this._computer.anchor); + if (loadingMessage) { + return result.slice(0).concat([loadingMessage]); + } + } + } + } + return result; + } + private _shouldShowAt(mouseEvent: IEditorMouseEvent): boolean { const targetType = mouseEvent.target.type; if (targetType === MouseTargetType.CONTENT_TEXT) { @@ -487,9 +502,7 @@ class EditorHoverStatusBar extends Disposable implements IEditorHoverStatusBar { class ContentHoverComputer implements IHoverComputer { - private readonly _editor: ICodeEditor; - private _result: IHoverPart[]; - private _anchor: HoverAnchor | null; + private _anchor: HoverAnchor | null = null; public get anchor(): HoverAnchor | null { return this._anchor; @@ -500,16 +513,9 @@ class ContentHoverComputer implements IHoverComputer { } constructor( - editor: ICodeEditor, + private readonly _editor: ICodeEditor, private readonly _participants: readonly IEditorHoverParticipant[] ) { - this._editor = editor; - this._result = []; - this._anchor = null; - } - - public clearResult(): void { - this._result = []; } private static _getLineDecorations(editor: IActiveCodeEditor, anchor: HoverAnchor): IModelDecoration[] { @@ -574,26 +580,4 @@ class ContentHoverComputer implements IHoverComputer { return coalesce(result); } - - public onResult(result: IHoverPart[]): void { - this._result = this._result.concat(result); - } - - public getResult(): IHoverPart[] { - return this._result.slice(0); - } - - public getResultWithLoadingMessage(): IHoverPart[] { - if (this._anchor) { - for (const participant of this._participants) { - if (participant.createLoadingMessage) { - const loadingMessage = participant.createLoadingMessage(this._anchor); - if (loadingMessage) { - return this._result.slice(0).concat([loadingMessage]); - } - } - } - } - return this._result.slice(0); - } } diff --git a/src/vs/editor/contrib/hover/hoverOperation.ts b/src/vs/editor/contrib/hover/hoverOperation.ts index 7a00f6ebdbbaa..7675099c7b3f5 100644 --- a/src/vs/editor/contrib/hover/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/hoverOperation.ts @@ -12,34 +12,14 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; export interface IHoverComputer { - /** * This is called after half the hover time */ computeAsync?: (token: CancellationToken) => AsyncIterableObject; - /** * This is called after all the hover time */ computeSync?: () => T[]; - - /** - * Clear the stored result. - */ - clearResult(): void; - - /** - * This is called whenever one of the compute* methods returns a truey value - */ - onResult: (result: T[]) => void; - - /** - * This is what will be sent as progress/complete to the computation promise - */ - getResult: () => T[]; - - getResultWithLoadingMessage: () => T[]; - } const enum ComputeHoverOperationState { @@ -85,6 +65,7 @@ export class HoverOperation extends Disposable { private _state = ComputeHoverOperationState.IDLE; private _asyncIterable: CancelableAsyncIterableObject | null = null; private _asyncIterableDone: boolean = false; + private _result: T[] = []; constructor( private readonly _editor: ICodeEditor, @@ -101,10 +82,6 @@ export class HoverOperation extends Disposable { super.dispose(); } - public clearResult(): void { - this._computer.clearResult(); - } - private get _hoverTime(): number { return this._editor.getOption(EditorOption.hover).delay; } @@ -133,7 +110,7 @@ export class HoverOperation extends Disposable { try { for await (const item of this._asyncIterable!) { if (item) { - this._computer.onResult([item]); + this._result.push(item); this._onProgress(); } } @@ -151,7 +128,7 @@ export class HoverOperation extends Disposable { private _triggerSyncComputation(): void { if (this._computer.computeSync) { - this._computer.onResult(this._computer.computeSync()); + this._result = this._result.concat(this._computer.computeSync()); } if (this._asyncIterableDone) { @@ -178,14 +155,14 @@ export class HoverOperation extends Disposable { } private _onComplete(): void { - this._onResult.fire(new HoverResult(this._computer.getResult(), true, false)); + this._onResult.fire(new HoverResult(this._result.slice(0), true, false)); } private _onProgress(): void { if (this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION_SHOWING_LOADING) { - this._onResult.fire(new HoverResult(this._computer.getResultWithLoadingMessage(), false, true)); + this._onResult.fire(new HoverResult(this._result.slice(0), false, true)); } else { - this._onResult.fire(new HoverResult(this._computer.getResult(), false, false)); + this._onResult.fire(new HoverResult(this._result.slice(0), false, false)); } } @@ -212,7 +189,7 @@ export class HoverOperation extends Disposable { } public cancel(): void { - this.clearResult(); + this._result = []; this._firstWaitScheduler.cancel(); this._secondWaitScheduler.cancel(); this._loadingMessageScheduler.cancel(); diff --git a/src/vs/editor/contrib/hover/marginHover.ts b/src/vs/editor/contrib/hover/marginHover.ts index 85e9abdc8a954..d9ccb5efd0756 100644 --- a/src/vs/editor/contrib/hover/marginHover.ts +++ b/src/vs/editor/contrib/hover/marginHover.ts @@ -6,13 +6,12 @@ import * as dom from 'vs/base/browser/dom'; import { asArray } from 'vs/base/common/arrays'; import { IMarkdownString, isEmptyMarkdownString } from 'vs/base/common/htmlContent'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { ILanguageService } from 'vs/editor/common/services/language'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation'; -import { Widget } from 'vs/base/browser/ui/widget'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; import { HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; @@ -22,7 +21,7 @@ export interface IHoverMessage { value: IMarkdownString; } -export class MarginHoverWidget extends Widget implements IOverlayWidget { +export class MarginHoverWidget extends Disposable implements IOverlayWidget { public static readonly ID = 'editor.contrib.modesGlyphHoverWidget'; @@ -31,7 +30,6 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { private _isVisible: boolean; private _messages: IHoverMessage[]; - private _lastLineNumber: number; private readonly _markdownRenderer: MarkdownRenderer; private readonly _computer: MarginHoverComputer; @@ -48,7 +46,6 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { this._isVisible = false; this._messages = []; - this._lastLineNumber = -1; this._hover = this._register(new HoverWidget()); this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); @@ -102,7 +99,7 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { } public startShowingAt(lineNumber: number): void { - if (this._lastLineNumber === lineNumber) { + if (this._computer.lineNumber === lineNumber) { // We have to show the widget at the exact same line number as before, so no work is needed return; } @@ -111,13 +108,12 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { this.hide(); - this._lastLineNumber = lineNumber; - this._computer.setLineNumber(lineNumber); + this._computer.lineNumber = lineNumber; this._hoverOperation.start(HoverStartMode.Delayed); } public hide(): void { - this._lastLineNumber = -1; + this._computer.lineNumber = -1; this._hoverOperation.cancel(); if (!this._isVisible) { return; @@ -130,7 +126,7 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { this._messages = result; if (this._messages.length > 0) { - this._renderMessages(this._lastLineNumber, this._messages); + this._renderMessages(this._computer.lineNumber, this._messages); } else { this.hide(); } @@ -179,23 +175,19 @@ export class MarginHoverWidget extends Widget implements IOverlayWidget { class MarginHoverComputer implements IHoverComputer { - private readonly _editor: ICodeEditor; - private _lineNumber: number; - private _result: IHoverMessage[]; + private _lineNumber: number = -1; - constructor(editor: ICodeEditor) { - this._editor = editor; - this._lineNumber = -1; - this._result = []; + public get lineNumber(): number { + return this._lineNumber; } - public setLineNumber(lineNumber: number): void { - this._lineNumber = lineNumber; - this._result = []; + public set lineNumber(value: number) { + this._lineNumber = value; } - public clearResult(): void { - this._result = []; + constructor( + private readonly _editor: ICodeEditor + ) { } public computeSync(): IHoverMessage[] { @@ -228,16 +220,4 @@ class MarginHoverComputer implements IHoverComputer { return result; } - - public onResult(result: IHoverMessage[]): void { - this._result = this._result.concat(result); - } - - public getResult(): IHoverMessage[] { - return this._result; - } - - public getResultWithLoadingMessage(): IHoverMessage[] { - return this.getResult(); - } } From 275b0c05eee77438976fb48a346f4a43d5abb894 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 7 Jan 2022 10:00:42 +0100 Subject: [PATCH 1113/2210] Simplifications --- src/vs/editor/contrib/hover/contentHover.ts | 40 ++++---- src/vs/editor/contrib/hover/hoverOperation.ts | 98 +++++++++---------- 2 files changed, 65 insertions(+), 73 deletions(-) diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index 2bbfa53979baf..6a4d047868877 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -77,10 +77,8 @@ export class ContentHoverController extends Disposable { this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer)); this._register(this._hoverOperation.onResult((result) => { - const actualResult = (result.hasLoadingMessage ? this._addLoadingMessage(result.value) : result.value); - this._withResult(actualResult, result.isComplete); + this._withResult(result.value, result.isComplete, result.hasLoadingMessage); })); - this._register(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); this._register(dom.addStandardDisposableListener(this._widget.getDomNode(), 'keydown', (e) => { if (e.equals(KeyCode.Escape)) { @@ -95,20 +93,6 @@ export class ContentHoverController extends Disposable { })); } - private _addLoadingMessage(result: IHoverPart[]): IHoverPart[] { - if (this._computer.anchor) { - for (const participant of this._participants) { - if (participant.createLoadingMessage) { - const loadingMessage = participant.createLoadingMessage(this._computer.anchor); - if (loadingMessage) { - return result.slice(0).concat([loadingMessage]); - } - } - } - } - return result; - } - private _shouldShowAt(mouseEvent: IEditorMouseEvent): boolean { const targetType = mouseEvent.target.type; if (targetType === MouseTargetType.CONTENT_TEXT) { @@ -232,13 +216,27 @@ export class ContentHoverController extends Disposable { return !!this._widget.colorPicker; } - private _withResult(result: IHoverPart[], complete: boolean): void { - this._messages = result; - this._messagesAreComplete = complete; + private _addLoadingMessage(result: IHoverPart[]): IHoverPart[] { + if (this._computer.anchor) { + for (const participant of this._participants) { + if (participant.createLoadingMessage) { + const loadingMessage = participant.createLoadingMessage(this._computer.anchor); + if (loadingMessage) { + return result.slice(0).concat([loadingMessage]); + } + } + } + } + return result; + } + + private _withResult(result: IHoverPart[], isComplete: boolean, hasLoadingMessage: boolean): void { + this._messages = (hasLoadingMessage ? this._addLoadingMessage(result) : result); + this._messagesAreComplete = isComplete; if (this._computer.anchor && this._messages.length > 0) { this._renderMessages(this._computer.anchor, this._messages); - } else if (complete) { + } else if (isComplete) { this.hide(); } } diff --git a/src/vs/editor/contrib/hover/hoverOperation.ts b/src/vs/editor/contrib/hover/hoverOperation.ts index 7675099c7b3f5..d270dea670dd9 100644 --- a/src/vs/editor/contrib/hover/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/hoverOperation.ts @@ -22,12 +22,12 @@ export interface IHoverComputer { computeSync?: () => T[]; } -const enum ComputeHoverOperationState { - IDLE = 0, - FIRST_WAIT = 1, - SECOND_WAIT = 2, - WAITING_FOR_ASYNC_COMPUTATION = 3, - WAITING_FOR_ASYNC_COMPUTATION_SHOWING_LOADING = 4, +const enum HoverOperationState { + Idle, + FirstWait, + SecondWait, + WaitingForAsync = 3, + WaitingForAsyncShowingLoading = 4, } export const enum HoverStartMode { @@ -60,9 +60,9 @@ export class HoverOperation extends Disposable { private readonly _firstWaitScheduler = this._register(new RunOnceScheduler(() => this._triggerAsyncComputation(), 0)); private readonly _secondWaitScheduler = this._register(new RunOnceScheduler(() => this._triggerSyncComputation(), 0)); - private readonly _loadingMessageScheduler = this._register(new RunOnceScheduler(() => this._showLoadingMessage(), 0)); + private readonly _loadingMessageScheduler = this._register(new RunOnceScheduler(() => this._triggerLoadingMessage(), 0)); - private _state = ComputeHoverOperationState.IDLE; + private _state = HoverOperationState.Idle; private _asyncIterable: CancelableAsyncIterableObject | null = null; private _asyncIterableDone: boolean = false; private _result: T[] = []; @@ -86,21 +86,28 @@ export class HoverOperation extends Disposable { return this._editor.getOption(EditorOption.hover).delay; } - private _firstWaitTime(): number { + private get _firstWaitTime(): number { return this._hoverTime / 2; } - private _secondWaitTime(): number { - return this._hoverTime / 2; + private get _secondWaitTime(): number { + return this._hoverTime - this._firstWaitTime; } - private _loadingMessageTime(): number { + private get _loadingMessageTime(): number { return 3 * this._hoverTime; } + private _setState(state: HoverOperationState, fireResult: boolean = true): void { + this._state = state; + if (fireResult) { + this._fireResult(); + } + } + private _triggerAsyncComputation(): void { - this._state = ComputeHoverOperationState.SECOND_WAIT; - this._secondWaitScheduler.schedule(this._secondWaitTime()); + this._setState(HoverOperationState.SecondWait); + this._secondWaitScheduler.schedule(this._secondWaitTime); if (this._computer.computeAsync) { this._asyncIterableDone = false; @@ -111,11 +118,15 @@ export class HoverOperation extends Disposable { for await (const item of this._asyncIterable!) { if (item) { this._result.push(item); - this._onProgress(); + this._fireResult(); } } this._asyncIterableDone = true; - this._withAsyncResult(); + + if (this._state === HoverOperationState.WaitingForAsync || this._state === HoverOperationState.WaitingForAsyncShowingLoading) { + this._setState(HoverOperationState.Idle); + } + } catch (e) { onUnexpectedError(e); } @@ -130,57 +141,40 @@ export class HoverOperation extends Disposable { if (this._computer.computeSync) { this._result = this._result.concat(this._computer.computeSync()); } - - if (this._asyncIterableDone) { - this._state = ComputeHoverOperationState.IDLE; - this._onComplete(); - } else { - this._state = ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION; - this._onProgress(); - } + this._setState(this._asyncIterableDone ? HoverOperationState.Idle : HoverOperationState.WaitingForAsync); } - private _showLoadingMessage(): void { - if (this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION) { - this._state = ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION_SHOWING_LOADING; - this._onProgress(); + private _triggerLoadingMessage(): void { + if (this._state === HoverOperationState.WaitingForAsync) { + this._setState(HoverOperationState.WaitingForAsyncShowingLoading); } } - private _withAsyncResult(): void { - if (this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION || this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION_SHOWING_LOADING) { - this._state = ComputeHoverOperationState.IDLE; - this._onComplete(); - } - } - - private _onComplete(): void { - this._onResult.fire(new HoverResult(this._result.slice(0), true, false)); - } - - private _onProgress(): void { - if (this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION_SHOWING_LOADING) { - this._onResult.fire(new HoverResult(this._result.slice(0), false, true)); - } else { - this._onResult.fire(new HoverResult(this._result.slice(0), false, false)); + private _fireResult(): void { + if (this._state === HoverOperationState.FirstWait || this._state === HoverOperationState.SecondWait) { + // Do not send out results before the hover time + return; } + const isComplete = (this._state === HoverOperationState.Idle); + const hasLoadingMessage = (this._state === HoverOperationState.WaitingForAsyncShowingLoading); + this._onResult.fire(new HoverResult(this._result.slice(0), isComplete, hasLoadingMessage)); } public start(mode: HoverStartMode): void { if (mode === HoverStartMode.Delayed) { - if (this._state === ComputeHoverOperationState.IDLE) { - this._state = ComputeHoverOperationState.FIRST_WAIT; - this._firstWaitScheduler.schedule(this._firstWaitTime()); - this._loadingMessageScheduler.schedule(this._loadingMessageTime()); + if (this._state === HoverOperationState.Idle) { + this._setState(HoverOperationState.FirstWait); + this._firstWaitScheduler.schedule(this._firstWaitTime); + this._loadingMessageScheduler.schedule(this._loadingMessageTime); } } else { switch (this._state) { - case ComputeHoverOperationState.IDLE: + case HoverOperationState.Idle: this._triggerAsyncComputation(); this._secondWaitScheduler.cancel(); this._triggerSyncComputation(); break; - case ComputeHoverOperationState.SECOND_WAIT: + case HoverOperationState.SecondWait: this._secondWaitScheduler.cancel(); this._triggerSyncComputation(); break; @@ -189,7 +183,6 @@ export class HoverOperation extends Disposable { } public cancel(): void { - this._result = []; this._firstWaitScheduler.cancel(); this._secondWaitScheduler.cancel(); this._loadingMessageScheduler.cancel(); @@ -197,7 +190,8 @@ export class HoverOperation extends Disposable { this._asyncIterable.cancel(); this._asyncIterable = null; } - this._state = ComputeHoverOperationState.IDLE; + this._result = []; + this._setState(HoverOperationState.Idle, false); } } From 1499478735a6ee5bf162d37f01b1d19c2bf27d2e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 7 Jan 2022 10:21:36 +0100 Subject: [PATCH 1114/2210] Remove code that was relying on 'ced-colorBox' (follow up from #132537) --- .../contrib/colorPicker/colorContributions.ts | 15 +++++++++------ .../editor/contrib/colorPicker/colorDetector.ts | 3 +++ src/vs/editor/contrib/colorPicker/colorPicker.css | 1 + src/vs/editor/contrib/hover/contentHover.ts | 11 +---------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/colorContributions.ts b/src/vs/editor/contrib/colorPicker/colorContributions.ts index 0423698694f64..0867134562477 100644 --- a/src/vs/editor/contrib/colorPicker/colorContributions.ts +++ b/src/vs/editor/contrib/colorPicker/colorContributions.ts @@ -3,17 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// import color detector contribution import { Disposable } from 'vs/base/common/lifecycle'; +import { ITextContentData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import 'vs/editor/contrib/colorPicker/colorDetector'; +import { ColorDecorationInjectedTextMarker } from 'vs/editor/contrib/colorPicker/colorDetector'; import { ModesHoverController } from 'vs/editor/contrib/hover/hover'; import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; - export class ColorContribution extends Disposable implements IEditorContribution { public static readonly ID: string = 'editor.contrib.colorContribution'; @@ -37,8 +36,12 @@ export class ColorContribution extends Disposable implements IEditorContribution return; } - const hoverOnColorDecorator = [...mouseEvent.target.element?.classList.values() || []].find(className => className.startsWith('ced-colorBox')); - if (!hoverOnColorDecorator) { + const detail = (mouseEvent.target.detail); + if (!detail || !detail.injectedText) { + return; + } + + if (detail.injectedText.options.attachedData !== ColorDecorationInjectedTextMarker) { return; } @@ -52,7 +55,7 @@ export class ColorContribution extends Disposable implements IEditorContribution } if (!hoverController.isColorPickerVisible()) { const range = new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1); - hoverController.showContentHover(range, HoverStartMode.Delayed, false); + hoverController.showContentHover(range, HoverStartMode.Immediate, false); } } } diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index b15d35102d74b..e2d3b148c4239 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -21,6 +21,8 @@ import { ColorProviderRegistry } from 'vs/editor/common/languages'; import { getColors, IColorData } from 'vs/editor/contrib/colorPicker/color'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +export const ColorDecorationInjectedTextMarker = Object.create({}); + const MAX_DECORATORS = 500; export class ColorDetector extends Disposable implements IEditorContribution { @@ -198,6 +200,7 @@ export class ColorDetector extends Disposable implements IEditorContribution { content: noBreakWhitespace, inlineClassName: `${ref.className} colorpicker-color-decoration`, inlineClassNameAffectsLetterSpacing: true, + attachedData: ColorDecorationInjectedTextMarker } } }); diff --git a/src/vs/editor/contrib/colorPicker/colorPicker.css b/src/vs/editor/contrib/colorPicker/colorPicker.css index 6f0d47697a912..6ce3952ae401e 100644 --- a/src/vs/editor/contrib/colorPicker/colorPicker.css +++ b/src/vs/editor/contrib/colorPicker/colorPicker.css @@ -20,6 +20,7 @@ height: 0.8em; line-height: 0.8em; display: inline-block; + cursor: pointer; } .hc-black .colorpicker-color-decoration, diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index 6a4d047868877..698c5fd1d986d 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -124,16 +124,7 @@ export class ContentHoverController extends Disposable { } if (this._shouldShowAt(mouseEvent) && mouseEvent.target.range) { - // TODO@rebornix. This should be removed if we move Color Picker out of Hover component. - // Check if mouse is hovering on color decorator - const hoverOnColorDecorator = [...mouseEvent.target.element?.classList.values() || []].find(className => className.startsWith('ced-colorBox')) - && mouseEvent.target.range.endColumn - mouseEvent.target.range.startColumn === 1; - const showAtRange = ( - hoverOnColorDecorator // shift the mouse focus by one as color decorator is a `before` decoration of next character. - ? new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1) - : mouseEvent.target.range - ); - anchorCandidates.push(new HoverRangeAnchor(0, showAtRange)); + anchorCandidates.push(new HoverRangeAnchor(0, mouseEvent.target.range)); } if (anchorCandidates.length === 0) { From 419e16a49c7bffc23b8e5f0f9f35ee34167f20e1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 7 Jan 2022 10:32:13 +0100 Subject: [PATCH 1115/2210] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index be6ce2e0f5738..883ae44cf6825 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.64.0", - "distro": "de857411bebe2132093f797a89437dd2b8199c10", + "distro": "3840fb71930c0410ea53305aef2f3d2af8e9b621", "author": { "name": "Microsoft Corporation" }, From 302c41cf4e4c3de5afa8583f6da68248cea0ae86 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 7 Jan 2022 11:11:01 +0100 Subject: [PATCH 1116/2210] Add command to drop all stashes --- extensions/git/package.json | 13 +++++++++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 23 +++++++++++++++++++++++ extensions/git/src/git.ts | 5 ++++- 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index da96c49e3a740..f5608879a1bc5 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -501,6 +501,11 @@ "title": "%command.stashDrop%", "category": "Git" }, + { + "command": "git.stashDropAll", + "title": "%command.stashDropAll%", + "category": "Git" + }, { "command": "git.timeline.openDiff", "title": "%command.timelineOpenDiff%", @@ -914,6 +919,10 @@ "command": "git.stashDrop", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.stashDropAll", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, { "command": "git.timeline.openDiff", "when": "false" @@ -1651,6 +1660,10 @@ { "command": "git.stashDrop", "group": "stash@7" + }, + { + "command": "git.stashDropAll", + "group": "stash@8" } ], "git.tags": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 481b1cb09c0f5..7d94943f947db 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -87,6 +87,7 @@ "command.stashApply": "Apply Stash...", "command.stashApplyLatest": "Apply Latest Stash", "command.stashDrop": "Drop Stash...", + "command.stashDropAll": "Drop All Stashes...", "command.timelineOpenDiff": "Open Changes", "command.timelineCopyCommitId": "Copy Commit ID", "command.timelineCopyCommitMessage": "Copy Commit Message", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3629e2d89cfc9..458a31fcb3548 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2604,6 +2604,29 @@ export class CommandCenter { await repository.dropStash(stash.index); } + @command('git.stashDropAll', { repository: true }) + async stashDropAll(repository: Repository): Promise { + const stashes = await repository.getStashes(); + + if (stashes.length === 0) { + window.showInformationMessage(localize('no stashes', "There are no stashes in the repository.")); + return; + } + + // request confirmation for the operation + const yes = localize('yes', "Yes"); + const question = stashes.length === 1 ? + localize('drop one stash', "Are you sure you want to drop ALL stashes? There is 1 stash that will be subject to pruning, and MAY BE IMPOSSIBLE TO RECOVER.") : + localize('drop all stashes', "Are you sure you want to drop ALL stashes? There are {0} stashes that will be subject to pruning, and MAY BE IMPOSSIBLE TO RECOVER.", stashes.length); + + const result = await window.showWarningMessage(question, yes); + if (result !== yes) { + return; + } + + await repository.dropStash(); + } + private async pickStash(repository: Repository, placeHolder: string): Promise { const stashes = await repository.getStashes(); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index eb546c026a0a2..b3b7f1c68deed 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1794,10 +1794,13 @@ export class Repository { } async dropStash(index?: number): Promise { - const args = ['stash', 'drop']; + const args = ['stash']; if (typeof index === 'number') { + args.push('drop'); args.push(`stash@{${index}}`); + } else { + args.push('clear'); } try { From 963ef01220409b2959df42f122d97b710063fa72 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 7 Jan 2022 12:17:36 +0100 Subject: [PATCH 1117/2210] set media-src to self for webClientServer --- src/vs/server/webClientServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/server/webClientServer.ts b/src/vs/server/webClientServer.ts index d2d03c7cc67d9..3774eed033629 100644 --- a/src/vs/server/webClientServer.ts +++ b/src/vs/server/webClientServer.ts @@ -209,7 +209,7 @@ export class WebClientServer { const cspDirectives = [ 'default-src \'self\';', 'img-src \'self\' https: data: blob:;', - 'media-src \'none\';', + 'media-src \'self\';', `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-cb2sg39EJV8ABaSNFfWu/ou8o1xVXYK7jp90oZ9vpcg=' http://${remoteAuthority};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html 'child-src \'self\';', `frame-src 'self' https://*.vscode-webview.net ${this._productService.webEndpointUrl || ''} data:;`, From 4f0ac125521573cf69e0bd29af75f6cd68b0d8f6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 7 Jan 2022 12:18:17 +0100 Subject: [PATCH 1118/2210] Use LifecyclePhase.Restored instead of LifecyclePhase.Eventually --- .../contrib/audioCues/browser/audioCues.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index d3217f86cefc5..a3b2104bc5c08 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -10,7 +10,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } fr import { AudioCueContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueContribution'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueContribution, LifecyclePhase.Eventually); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueContribution, LifecyclePhase.Restored); Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'properties': { From e1800b22045b81da491e55c9bf9775cddbb710f9 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 7 Jan 2022 14:52:33 +0100 Subject: [PATCH 1119/2210] Add debug log for tree drag mime types --- src/vs/workbench/browser/parts/views/treeView.ts | 5 ++++- src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 6c8d34d2d0757..991dfd07b2627 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -61,6 +61,7 @@ import { CodeDataTransfers, fillEditorsDragData } from 'vs/workbench/browser/dnd import { Schemas } from 'vs/base/common/network'; import { ITreeViewsDragAndDropService } from 'vs/workbench/services/views/common/treeViewsDragAndDropService'; import { generateUuid } from 'vs/base/common/uuid'; +import { ILogService } from 'vs/platform/log/common/log'; export class TreeViewPane extends ViewPane { @@ -1244,7 +1245,8 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { private readonly treeId: string, @ILabelService private readonly labelService: ILabelService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITreeViewsDragAndDropService private readonly treeViewsDragAndDropService: ITreeViewsDragAndDropService) { + @ITreeViewsDragAndDropService private readonly treeViewsDragAndDropService: ITreeViewsDragAndDropService, + @ILogService private readonly logService: ILogService) { this.treeMimeType = `tree/${treeId.toLowerCase()}`; } @@ -1298,6 +1300,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + this.logService.debug(`TreeView dragged mime types: ${originalEvent.dataTransfer?.types.join(', ')}`); const dndController = this.dndController; if (!dndController || !originalEvent.dataTransfer || (dndController.supportedMimeTypes.length === 0)) { return false; diff --git a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts index cd2889c7cf16e..63170e0b49842 100644 --- a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts +++ b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts @@ -87,6 +87,11 @@ declare module 'vscode' { * * Each tree will automatically support drops from it's own `DragAndDropController`. To support drops from other trees, * you will need to add the mime type of that tree. The mime type of a tree is of the format `tree/treeidlowercase`. + * + * To learn the mime type of a dragged item: + * 1. Set up your `DragAndDropController` + * 2. Use the Developer: Set Log Level... command to set the level to "Debug" + * 3. Open the developer tools and drag the item with unknown mime type over your tree. The mime types will be logged to the developer console */ readonly supportedMimeTypes: string[]; From 4d0b81edfba9e36591449e12d27f2f1721fb0f64 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 7 Jan 2022 15:07:33 +0100 Subject: [PATCH 1120/2210] Fix task smoke test Fixes #140110 --- .../workspace.tasks.test.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index b1274fdbb8753..2e374955f6933 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -76,7 +76,7 @@ import { assertNoRpc } from '../utils'; }); }); - test.skip('dependsOn task should start with a different processId (#118256)', async () => { + test('dependsOn task should start with a different processId (#118256)', async () => { // Set up dependsOn task by creating tasks.json since this is not possible via the API // Tasks API const tasksConfig = workspace.getConfiguration('tasks'); @@ -97,11 +97,16 @@ import { assertNoRpc } from '../utils'; } ], ConfigurationTarget.Workspace); - // Run the task - commands.executeCommand('workbench.action.tasks.runTask', 'Run this task'); + const waitForTaskToFinish = new Promise(resolve => { + tasks.onDidEndTask(e => { + if (e.execution.task.name === 'Run this task') { + resolve(); + } + }); + }); - // Listen for first task and verify valid process ID - const startEvent1 = await new Promise(r => { + const waitForStartEvent1 = new Promise(r => { + // Listen for first task and verify valid process ID const listener = tasks.onDidStartTaskProcess(async (e) => { if (e.execution.task.name === 'taskToDependOn') { listener.dispose(); @@ -109,11 +114,10 @@ import { assertNoRpc } from '../utils'; } }); }); - assert.ok(startEvent1.processId); - // Listen for second task, verify valid process ID and that it's not the process ID of - // the first task - const startEvent2 = await new Promise(r => { + const waitForStartEvent2 = new Promise(r => { + // Listen for second task, verify valid process ID and that it's not the process ID of + // the first task const listener = tasks.onDidStartTaskProcess(async (e) => { if (e.execution.task.name === 'Run this task') { listener.dispose(); @@ -121,9 +125,17 @@ import { assertNoRpc } from '../utils'; } }); }); + + // Run the task + commands.executeCommand('workbench.action.tasks.runTask', 'Run this task'); + + const startEvent1 = await waitForStartEvent1; + assert.ok(startEvent1.processId); + + const startEvent2 = await waitForStartEvent2; assert.ok(startEvent2.processId); assert.notStrictEqual(startEvent1.processId, startEvent2.processId); - + await waitForTaskToFinish; // Clear out tasks config await tasksConfig.update('tasks', []); }); From 8d5f0db240d5f90f7b585722c2ed0afd8b5a6532 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 7 Jan 2022 15:13:11 +0100 Subject: [PATCH 1121/2210] Fixes #138746 by updating confusables. --- src/vs/base/common/strings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index d30bc5257aeb8..3730de28ce338 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -1048,7 +1048,7 @@ export class AmbiguousCharacters { // Generated using https://github.com/hediet/vscode-unicode-data // Stored as key1, value1, key2, value2, ... return JSON.parse( - '{"_common":[8232,32,8233,32,5760,32,8192,32,8193,32,8194,32,8195,32,8196,32,8197,32,8198,32,8200,32,8201,32,8202,32,8287,32,8199,32,8239,32,2042,95,65101,95,65102,95,65103,95,8208,45,8209,45,8210,45,65112,45,1748,45,8259,45,727,45,8722,45,10134,45,11450,45,1549,44,1643,44,8218,44,184,44,42233,44,894,59,2307,58,2691,58,1417,58,1795,58,1796,58,5868,58,65072,58,6147,58,6153,58,8282,58,1475,58,760,58,42889,58,8758,58,720,58,42237,58,451,33,11601,33,660,63,577,63,2429,63,5038,63,42731,63,119149,46,8228,46,1793,46,1794,46,42510,46,68176,46,1632,46,1776,46,42232,46,1373,96,65287,96,8219,96,8242,96,1370,96,1523,96,8175,96,65344,96,900,96,8189,96,8125,96,8127,96,8190,96,697,96,884,96,712,96,714,96,715,96,756,96,699,96,701,96,700,96,702,96,42892,96,1497,96,2036,96,2037,96,5194,96,5836,96,94033,96,94034,96,65339,40,10088,40,10098,40,12308,40,64830,40,65341,41,10089,41,10099,41,12309,41,64831,41,10100,123,119060,123,10101,125,8270,42,1645,42,8727,42,66335,42,5941,47,8257,47,8725,47,8260,47,9585,47,10187,47,10744,47,119354,47,12755,47,12339,47,11462,47,20031,47,12035,47,65340,92,65128,92,8726,92,10189,92,10741,92,10745,92,119311,92,119355,92,12756,92,20022,92,12034,92,42872,38,708,94,710,94,5869,43,10133,43,66203,43,8249,60,10094,60,706,60,119350,60,5176,60,5810,60,5120,61,11840,61,12448,61,42239,61,8250,62,10095,62,707,62,119351,62,5171,62,94015,62,8275,126,732,126,8128,126,8764,126,120784,50,120794,50,120804,50,120814,50,120824,50,130034,50,42842,50,423,50,1000,50,42564,50,5311,50,42735,50,119302,51,120785,51,120795,51,120805,51,120815,51,120825,51,130035,51,42923,51,540,51,439,51,42858,51,11468,51,1248,51,94011,51,71882,51,120786,52,120796,52,120806,52,120816,52,120826,52,130036,52,5070,52,71855,52,120787,53,120797,53,120807,53,120817,53,120827,53,130037,53,444,53,71867,53,120788,54,120798,54,120808,54,120818,54,120828,54,130038,54,11474,54,5102,54,71893,54,119314,55,120789,55,120799,55,120809,55,120819,55,120829,55,130039,55,66770,55,71878,55,2819,56,2538,56,2666,56,125131,56,120790,56,120800,56,120810,56,120820,56,120830,56,130040,56,547,56,546,56,66330,56,2663,57,2920,57,2541,57,3437,57,120791,57,120801,57,120811,57,120821,57,120831,57,130041,57,42862,57,11466,57,71884,57,71852,57,71894,57,9082,97,65345,97,119834,97,119886,97,119938,97,119990,97,120042,97,120094,97,120146,97,120198,97,120250,97,120302,97,120354,97,120406,97,120458,97,593,97,945,97,120514,97,120572,97,120630,97,120688,97,120746,97,65313,65,119808,65,119860,65,119912,65,119964,65,120016,65,120068,65,120120,65,120172,65,120224,65,120276,65,120328,65,120380,65,120432,65,913,65,120488,65,120546,65,120604,65,120662,65,120720,65,5034,65,5573,65,42222,65,94016,65,66208,65,119835,98,119887,98,119939,98,119991,98,120043,98,120095,98,120147,98,120199,98,120251,98,120303,98,120355,98,120407,98,120459,98,388,98,5071,98,5234,98,5551,98,65314,66,8492,66,119809,66,119861,66,119913,66,120017,66,120069,66,120121,66,120173,66,120225,66,120277,66,120329,66,120381,66,120433,66,42932,66,914,66,120489,66,120547,66,120605,66,120663,66,120721,66,5108,66,5623,66,42192,66,66178,66,66209,66,66305,66,65347,99,8573,99,119836,99,119888,99,119940,99,119992,99,120044,99,120096,99,120148,99,120200,99,120252,99,120304,99,120356,99,120408,99,120460,99,7428,99,1010,99,11429,99,43951,99,66621,99,128844,67,71922,67,71913,67,65315,67,8557,67,8450,67,8493,67,119810,67,119862,67,119914,67,119966,67,120018,67,120174,67,120226,67,120278,67,120330,67,120382,67,120434,67,1017,67,11428,67,5087,67,42202,67,66210,67,66306,67,66581,67,66844,67,8574,100,8518,100,119837,100,119889,100,119941,100,119993,100,120045,100,120097,100,120149,100,120201,100,120253,100,120305,100,120357,100,120409,100,120461,100,1281,100,5095,100,5231,100,42194,100,8558,68,8517,68,119811,68,119863,68,119915,68,119967,68,120019,68,120071,68,120123,68,120175,68,120227,68,120279,68,120331,68,120383,68,120435,68,5024,68,5598,68,5610,68,42195,68,8494,101,65349,101,8495,101,8519,101,119838,101,119890,101,119942,101,120046,101,120098,101,120150,101,120202,101,120254,101,120306,101,120358,101,120410,101,120462,101,43826,101,1213,101,8959,69,65317,69,8496,69,119812,69,119864,69,119916,69,120020,69,120072,69,120124,69,120176,69,120228,69,120280,69,120332,69,120384,69,120436,69,917,69,120492,69,120550,69,120608,69,120666,69,120724,69,11577,69,5036,69,42224,69,71846,69,71854,69,66182,69,119839,102,119891,102,119943,102,119995,102,120047,102,120099,102,120151,102,120203,102,120255,102,120307,102,120359,102,120411,102,120463,102,43829,102,42905,102,383,102,7837,102,1412,102,119315,70,8497,70,119813,70,119865,70,119917,70,120021,70,120073,70,120125,70,120177,70,120229,70,120281,70,120333,70,120385,70,120437,70,42904,70,988,70,120778,70,5556,70,42205,70,71874,70,71842,70,66183,70,66213,70,66853,70,65351,103,8458,103,119840,103,119892,103,119944,103,120048,103,120100,103,120152,103,120204,103,120256,103,120308,103,120360,103,120412,103,120464,103,609,103,7555,103,397,103,1409,103,119814,71,119866,71,119918,71,119970,71,120022,71,120074,71,120126,71,120178,71,120230,71,120282,71,120334,71,120386,71,120438,71,1292,71,5056,71,5107,71,42198,71,65352,104,8462,104,119841,104,119945,104,119997,104,120049,104,120101,104,120153,104,120205,104,120257,104,120309,104,120361,104,120413,104,120465,104,1211,104,1392,104,5058,104,65320,72,8459,72,8460,72,8461,72,119815,72,119867,72,119919,72,120023,72,120179,72,120231,72,120283,72,120335,72,120387,72,120439,72,919,72,120494,72,120552,72,120610,72,120668,72,120726,72,11406,72,5051,72,5500,72,42215,72,66255,72,731,105,9075,105,65353,105,8560,105,8505,105,8520,105,119842,105,119894,105,119946,105,119998,105,120050,105,120102,105,120154,105,120206,105,120258,105,120310,105,120362,105,120414,105,120466,105,120484,105,618,105,617,105,953,105,8126,105,890,105,120522,105,120580,105,120638,105,120696,105,120754,105,1110,105,42567,105,1231,105,43893,105,5029,105,71875,105,65354,106,8521,106,119843,106,119895,106,119947,106,119999,106,120051,106,120103,106,120155,106,120207,106,120259,106,120311,106,120363,106,120415,106,120467,106,1011,106,1112,106,65322,74,119817,74,119869,74,119921,74,119973,74,120025,74,120077,74,120129,74,120181,74,120233,74,120285,74,120337,74,120389,74,120441,74,42930,74,895,74,1032,74,5035,74,5261,74,42201,74,119844,107,119896,107,119948,107,120000,107,120052,107,120104,107,120156,107,120208,107,120260,107,120312,107,120364,107,120416,107,120468,107,8490,75,65323,75,119818,75,119870,75,119922,75,119974,75,120026,75,120078,75,120130,75,120182,75,120234,75,120286,75,120338,75,120390,75,120442,75,922,75,120497,75,120555,75,120613,75,120671,75,120729,75,11412,75,5094,75,5845,75,42199,75,66840,75,1472,124,8739,124,9213,124,65512,124,1633,124,1777,124,66336,124,125127,124,120783,124,120793,124,120803,124,120813,124,120823,124,130033,124,65321,124,8544,124,8464,124,8465,124,119816,124,119868,124,119920,124,120024,124,120128,124,120180,124,120232,124,120284,124,120336,124,120388,124,120440,124,406,124,65356,124,8572,124,8467,124,119845,124,119897,124,119949,124,120001,124,120053,124,120105,124,120157,124,120209,124,120261,124,120313,124,120365,124,120417,124,120469,124,448,124,120496,124,120554,124,120612,124,120670,124,120728,124,11410,124,1030,124,1216,124,1493,124,1503,124,1575,124,126464,124,126592,124,65166,124,65165,124,1994,124,11599,124,5825,124,42226,124,93992,124,66186,124,66313,124,119338,76,8556,76,8466,76,119819,76,119871,76,119923,76,120027,76,120079,76,120131,76,120183,76,120235,76,120287,76,120339,76,120391,76,120443,76,11472,76,5086,76,5290,76,42209,76,93974,76,71843,76,71858,76,66587,76,66854,76,65325,77,8559,77,8499,77,119820,77,119872,77,119924,77,120028,77,120080,77,120132,77,120184,77,120236,77,120288,77,120340,77,120392,77,120444,77,924,77,120499,77,120557,77,120615,77,120673,77,120731,77,1018,77,11416,77,5047,77,5616,77,5846,77,42207,77,66224,77,66321,77,119847,110,119899,110,119951,110,120003,110,120055,110,120107,110,120159,110,120211,110,120263,110,120315,110,120367,110,120419,110,120471,110,1400,110,1404,110,65326,78,8469,78,119821,78,119873,78,119925,78,119977,78,120029,78,120081,78,120185,78,120237,78,120289,78,120341,78,120393,78,120445,78,925,78,120500,78,120558,78,120616,78,120674,78,120732,78,11418,78,42208,78,66835,78,3074,111,3202,111,3330,111,3458,111,2406,111,2662,111,2790,111,3046,111,3174,111,3302,111,3430,111,3664,111,3792,111,4160,111,1637,111,1781,111,65359,111,8500,111,119848,111,119900,111,119952,111,120056,111,120108,111,120160,111,120212,111,120264,111,120316,111,120368,111,120420,111,120472,111,7439,111,7441,111,43837,111,959,111,120528,111,120586,111,120644,111,120702,111,120760,111,963,111,120532,111,120590,111,120648,111,120706,111,120764,111,11423,111,4351,111,1413,111,1505,111,1607,111,126500,111,126564,111,126596,111,65259,111,65260,111,65258,111,65257,111,1726,111,64428,111,64429,111,64427,111,64426,111,1729,111,64424,111,64425,111,64423,111,64422,111,1749,111,3360,111,4125,111,66794,111,71880,111,71895,111,66604,111,1984,79,2534,79,2918,79,12295,79,70864,79,71904,79,120782,79,120792,79,120802,79,120812,79,120822,79,130032,79,65327,79,119822,79,119874,79,119926,79,119978,79,120030,79,120082,79,120134,79,120186,79,120238,79,120290,79,120342,79,120394,79,120446,79,927,79,120502,79,120560,79,120618,79,120676,79,120734,79,11422,79,1365,79,11604,79,4816,79,2848,79,66754,79,42227,79,71861,79,66194,79,66219,79,66564,79,66838,79,9076,112,65360,112,119849,112,119901,112,119953,112,120005,112,120057,112,120109,112,120161,112,120213,112,120265,112,120317,112,120369,112,120421,112,120473,112,961,112,120530,112,120544,112,120588,112,120602,112,120646,112,120660,112,120704,112,120718,112,120762,112,120776,112,11427,112,65328,80,8473,80,119823,80,119875,80,119927,80,119979,80,120031,80,120083,80,120187,80,120239,80,120291,80,120343,80,120395,80,120447,80,929,80,120504,80,120562,80,120620,80,120678,80,120736,80,11426,80,5090,80,5229,80,42193,80,66197,80,119850,113,119902,113,119954,113,120006,113,120058,113,120110,113,120162,113,120214,113,120266,113,120318,113,120370,113,120422,113,120474,113,1307,113,1379,113,1382,113,8474,81,119824,81,119876,81,119928,81,119980,81,120032,81,120084,81,120188,81,120240,81,120292,81,120344,81,120396,81,120448,81,11605,81,119851,114,119903,114,119955,114,120007,114,120059,114,120111,114,120163,114,120215,114,120267,114,120319,114,120371,114,120423,114,120475,114,43847,114,43848,114,7462,114,11397,114,43905,114,119318,82,8475,82,8476,82,8477,82,119825,82,119877,82,119929,82,120033,82,120189,82,120241,82,120293,82,120345,82,120397,82,120449,82,422,82,5025,82,5074,82,66740,82,5511,82,42211,82,94005,82,65363,115,119852,115,119904,115,119956,115,120008,115,120060,115,120112,115,120164,115,120216,115,120268,115,120320,115,120372,115,120424,115,120476,115,42801,115,445,115,1109,115,43946,115,71873,115,66632,115,65331,83,119826,83,119878,83,119930,83,119982,83,120034,83,120086,83,120138,83,120190,83,120242,83,120294,83,120346,83,120398,83,120450,83,1029,83,1359,83,5077,83,5082,83,42210,83,94010,83,66198,83,66592,83,119853,116,119905,116,119957,116,120009,116,120061,116,120113,116,120165,116,120217,116,120269,116,120321,116,120373,116,120425,116,120477,116,8868,84,10201,84,128872,84,65332,84,119827,84,119879,84,119931,84,119983,84,120035,84,120087,84,120139,84,120191,84,120243,84,120295,84,120347,84,120399,84,120451,84,932,84,120507,84,120565,84,120623,84,120681,84,120739,84,11430,84,5026,84,42196,84,93962,84,71868,84,66199,84,66225,84,66325,84,119854,117,119906,117,119958,117,120010,117,120062,117,120114,117,120166,117,120218,117,120270,117,120322,117,120374,117,120426,117,120478,117,42911,117,7452,117,43854,117,43858,117,651,117,965,117,120534,117,120592,117,120650,117,120708,117,120766,117,1405,117,66806,117,71896,117,8746,85,8899,85,119828,85,119880,85,119932,85,119984,85,120036,85,120088,85,120140,85,120192,85,120244,85,120296,85,120348,85,120400,85,120452,85,1357,85,4608,85,66766,85,5196,85,42228,85,94018,85,71864,85,8744,118,8897,118,65366,118,8564,118,119855,118,119907,118,119959,118,120011,118,120063,118,120115,118,120167,118,120219,118,120271,118,120323,118,120375,118,120427,118,120479,118,7456,118,957,118,120526,118,120584,118,120642,118,120700,118,120758,118,1141,118,1496,118,71430,118,43945,118,71872,118,119309,86,1639,86,1783,86,8548,86,119829,86,119881,86,119933,86,119985,86,120037,86,120089,86,120141,86,120193,86,120245,86,120297,86,120349,86,120401,86,120453,86,1140,86,11576,86,5081,86,5167,86,42719,86,42214,86,93960,86,71840,86,66845,86,623,119,119856,119,119908,119,119960,119,120012,119,120064,119,120116,119,120168,119,120220,119,120272,119,120324,119,120376,119,120428,119,120480,119,7457,119,1121,119,1309,119,1377,119,71434,119,71438,119,71439,119,43907,119,71919,87,71910,87,119830,87,119882,87,119934,87,119986,87,120038,87,120090,87,120142,87,120194,87,120246,87,120298,87,120350,87,120402,87,120454,87,1308,87,5043,87,5076,87,42218,87,5742,120,10539,120,10540,120,10799,120,65368,120,8569,120,119857,120,119909,120,119961,120,120013,120,120065,120,120117,120,120169,120,120221,120,120273,120,120325,120,120377,120,120429,120,120481,120,5441,120,5501,120,5741,88,9587,88,66338,88,71916,88,65336,88,8553,88,119831,88,119883,88,119935,88,119987,88,120039,88,120091,88,120143,88,120195,88,120247,88,120299,88,120351,88,120403,88,120455,88,42931,88,935,88,120510,88,120568,88,120626,88,120684,88,120742,88,11436,88,11613,88,5815,88,42219,88,66192,88,66228,88,66327,88,66855,88,611,121,7564,121,65369,121,119858,121,119910,121,119962,121,120014,121,120066,121,120118,121,120170,121,120222,121,120274,121,120326,121,120378,121,120430,121,120482,121,655,121,7935,121,43866,121,947,121,8509,121,120516,121,120574,121,120632,121,120690,121,120748,121,1199,121,4327,121,71900,121,65337,89,119832,89,119884,89,119936,89,119988,89,120040,89,120092,89,120144,89,120196,89,120248,89,120300,89,120352,89,120404,89,120456,89,933,89,978,89,120508,89,120566,89,120624,89,120682,89,120740,89,11432,89,1198,89,5033,89,5053,89,42220,89,94019,89,71844,89,66226,89,119859,122,119911,122,119963,122,120015,122,120067,122,120119,122,120171,122,120223,122,120275,122,120327,122,120379,122,120431,122,120483,122,7458,122,43923,122,71876,122,66293,90,71909,90,65338,90,8484,90,8488,90,119833,90,119885,90,119937,90,119989,90,120041,90,120197,90,120249,90,120301,90,120353,90,120405,90,120457,90,918,90,120493,90,120551,90,120609,90,120667,90,120725,90,5059,90,42204,90,71849,90],"_default":[160,32,8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"cs":[65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89],"de":[65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89],"es":[8211,45,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"fr":[65306,58,65281,33,8216,96,8245,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"it":[160,32,8211,45,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"ja":[8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"ko":[8211,45,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"pl":[65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"pt-BR":[65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"qps-ploc":[160,32,8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"ru":[65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,305,105,921,124,1009,112,215,120],"tr":[160,32,8211,45,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"zh-hans":[65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89],"zh-hant":[8211,45,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,124,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89]}' + '{\"_common\":[8232,32,8233,32,5760,32,8192,32,8193,32,8194,32,8195,32,8196,32,8197,32,8198,32,8200,32,8201,32,8202,32,8287,32,8199,32,8239,32,2042,95,65101,95,65102,95,65103,95,8208,45,8209,45,8210,45,65112,45,1748,45,8259,45,727,45,8722,45,10134,45,11450,45,1549,44,1643,44,8218,44,184,44,42233,44,894,59,2307,58,2691,58,1417,58,1795,58,1796,58,5868,58,65072,58,6147,58,6153,58,8282,58,1475,58,760,58,42889,58,8758,58,720,58,42237,58,451,33,11601,33,660,63,577,63,2429,63,5038,63,42731,63,119149,46,8228,46,1793,46,1794,46,42510,46,68176,46,1632,46,1776,46,42232,46,1373,96,65287,96,8219,96,8242,96,1370,96,1523,96,8175,96,900,96,8189,96,8125,96,8127,96,8190,96,697,96,884,96,712,96,714,96,715,96,756,96,699,96,701,96,700,96,702,96,42892,96,1497,96,2036,96,2037,96,5194,96,5836,96,94033,96,94034,96,65339,91,10088,40,10098,40,12308,40,64830,40,65341,93,10089,41,10099,41,12309,41,64831,41,10100,123,119060,123,10101,125,65342,94,8270,42,1645,42,8727,42,66335,42,5941,47,8257,47,8725,47,8260,47,9585,47,10187,47,10744,47,119354,47,12755,47,12339,47,11462,47,20031,47,12035,47,65340,92,65128,92,8726,92,10189,92,10741,92,10745,92,119311,92,119355,92,12756,92,20022,92,12034,92,42872,38,708,94,710,94,5869,43,10133,43,66203,43,8249,60,10094,60,706,60,119350,60,5176,60,5810,60,5120,61,11840,61,12448,61,42239,61,8250,62,10095,62,707,62,119351,62,5171,62,94015,62,8275,126,732,126,8128,126,8764,126,65372,124,65293,45,120784,50,120794,50,120804,50,120814,50,120824,50,130034,50,42842,50,423,50,1000,50,42564,50,5311,50,42735,50,119302,51,120785,51,120795,51,120805,51,120815,51,120825,51,130035,51,42923,51,540,51,439,51,42858,51,11468,51,1248,51,94011,51,71882,51,120786,52,120796,52,120806,52,120816,52,120826,52,130036,52,5070,52,71855,52,120787,53,120797,53,120807,53,120817,53,120827,53,130037,53,444,53,71867,53,120788,54,120798,54,120808,54,120818,54,120828,54,130038,54,11474,54,5102,54,71893,54,119314,55,120789,55,120799,55,120809,55,120819,55,120829,55,130039,55,66770,55,71878,55,2819,56,2538,56,2666,56,125131,56,120790,56,120800,56,120810,56,120820,56,120830,56,130040,56,547,56,546,56,66330,56,2663,57,2920,57,2541,57,3437,57,120791,57,120801,57,120811,57,120821,57,120831,57,130041,57,42862,57,11466,57,71884,57,71852,57,71894,57,9082,97,65345,97,119834,97,119886,97,119938,97,119990,97,120042,97,120094,97,120146,97,120198,97,120250,97,120302,97,120354,97,120406,97,120458,97,593,97,945,97,120514,97,120572,97,120630,97,120688,97,120746,97,65313,65,119808,65,119860,65,119912,65,119964,65,120016,65,120068,65,120120,65,120172,65,120224,65,120276,65,120328,65,120380,65,120432,65,913,65,120488,65,120546,65,120604,65,120662,65,120720,65,5034,65,5573,65,42222,65,94016,65,66208,65,119835,98,119887,98,119939,98,119991,98,120043,98,120095,98,120147,98,120199,98,120251,98,120303,98,120355,98,120407,98,120459,98,388,98,5071,98,5234,98,5551,98,65314,66,8492,66,119809,66,119861,66,119913,66,120017,66,120069,66,120121,66,120173,66,120225,66,120277,66,120329,66,120381,66,120433,66,42932,66,914,66,120489,66,120547,66,120605,66,120663,66,120721,66,5108,66,5623,66,42192,66,66178,66,66209,66,66305,66,65347,99,8573,99,119836,99,119888,99,119940,99,119992,99,120044,99,120096,99,120148,99,120200,99,120252,99,120304,99,120356,99,120408,99,120460,99,7428,99,1010,99,11429,99,43951,99,66621,99,128844,67,71922,67,71913,67,65315,67,8557,67,8450,67,8493,67,119810,67,119862,67,119914,67,119966,67,120018,67,120174,67,120226,67,120278,67,120330,67,120382,67,120434,67,1017,67,11428,67,5087,67,42202,67,66210,67,66306,67,66581,67,66844,67,8574,100,8518,100,119837,100,119889,100,119941,100,119993,100,120045,100,120097,100,120149,100,120201,100,120253,100,120305,100,120357,100,120409,100,120461,100,1281,100,5095,100,5231,100,42194,100,8558,68,8517,68,119811,68,119863,68,119915,68,119967,68,120019,68,120071,68,120123,68,120175,68,120227,68,120279,68,120331,68,120383,68,120435,68,5024,68,5598,68,5610,68,42195,68,8494,101,65349,101,8495,101,8519,101,119838,101,119890,101,119942,101,120046,101,120098,101,120150,101,120202,101,120254,101,120306,101,120358,101,120410,101,120462,101,43826,101,1213,101,8959,69,65317,69,8496,69,119812,69,119864,69,119916,69,120020,69,120072,69,120124,69,120176,69,120228,69,120280,69,120332,69,120384,69,120436,69,917,69,120492,69,120550,69,120608,69,120666,69,120724,69,11577,69,5036,69,42224,69,71846,69,71854,69,66182,69,119839,102,119891,102,119943,102,119995,102,120047,102,120099,102,120151,102,120203,102,120255,102,120307,102,120359,102,120411,102,120463,102,43829,102,42905,102,383,102,7837,102,1412,102,119315,70,8497,70,119813,70,119865,70,119917,70,120021,70,120073,70,120125,70,120177,70,120229,70,120281,70,120333,70,120385,70,120437,70,42904,70,988,70,120778,70,5556,70,42205,70,71874,70,71842,70,66183,70,66213,70,66853,70,65351,103,8458,103,119840,103,119892,103,119944,103,120048,103,120100,103,120152,103,120204,103,120256,103,120308,103,120360,103,120412,103,120464,103,609,103,7555,103,397,103,1409,103,119814,71,119866,71,119918,71,119970,71,120022,71,120074,71,120126,71,120178,71,120230,71,120282,71,120334,71,120386,71,120438,71,1292,71,5056,71,5107,71,42198,71,65352,104,8462,104,119841,104,119945,104,119997,104,120049,104,120101,104,120153,104,120205,104,120257,104,120309,104,120361,104,120413,104,120465,104,1211,104,1392,104,5058,104,65320,72,8459,72,8460,72,8461,72,119815,72,119867,72,119919,72,120023,72,120179,72,120231,72,120283,72,120335,72,120387,72,120439,72,919,72,120494,72,120552,72,120610,72,120668,72,120726,72,11406,72,5051,72,5500,72,42215,72,66255,72,731,105,9075,105,65353,105,8560,105,8505,105,8520,105,119842,105,119894,105,119946,105,119998,105,120050,105,120102,105,120154,105,120206,105,120258,105,120310,105,120362,105,120414,105,120466,105,120484,105,618,105,617,105,953,105,8126,105,890,105,120522,105,120580,105,120638,105,120696,105,120754,105,1110,105,42567,105,1231,105,43893,105,5029,105,71875,105,65354,106,8521,106,119843,106,119895,106,119947,106,119999,106,120051,106,120103,106,120155,106,120207,106,120259,106,120311,106,120363,106,120415,106,120467,106,1011,106,1112,106,65322,74,119817,74,119869,74,119921,74,119973,74,120025,74,120077,74,120129,74,120181,74,120233,74,120285,74,120337,74,120389,74,120441,74,42930,74,895,74,1032,74,5035,74,5261,74,42201,74,119844,107,119896,107,119948,107,120000,107,120052,107,120104,107,120156,107,120208,107,120260,107,120312,107,120364,107,120416,107,120468,107,8490,75,65323,75,119818,75,119870,75,119922,75,119974,75,120026,75,120078,75,120130,75,120182,75,120234,75,120286,75,120338,75,120390,75,120442,75,922,75,120497,75,120555,75,120613,75,120671,75,120729,75,11412,75,5094,75,5845,75,42199,75,66840,75,1472,108,8739,73,9213,73,65512,73,1633,108,1777,73,66336,108,125127,108,120783,73,120793,73,120803,73,120813,73,120823,73,130033,73,65321,73,8544,73,8464,73,8465,73,119816,73,119868,73,119920,73,120024,73,120128,73,120180,73,120232,73,120284,73,120336,73,120388,73,120440,73,65356,108,8572,73,8467,108,119845,108,119897,108,119949,108,120001,108,120053,108,120105,73,120157,73,120209,73,120261,73,120313,73,120365,73,120417,73,120469,73,448,73,120496,73,120554,73,120612,73,120670,73,120728,73,11410,73,1030,73,1216,73,1493,108,1503,108,1575,108,126464,108,126592,108,65166,108,65165,108,1994,108,11599,73,5825,73,42226,73,93992,73,66186,124,66313,124,119338,76,8556,76,8466,76,119819,76,119871,76,119923,76,120027,76,120079,76,120131,76,120183,76,120235,76,120287,76,120339,76,120391,76,120443,76,11472,76,5086,76,5290,76,42209,76,93974,76,71843,76,71858,76,66587,76,66854,76,65325,77,8559,77,8499,77,119820,77,119872,77,119924,77,120028,77,120080,77,120132,77,120184,77,120236,77,120288,77,120340,77,120392,77,120444,77,924,77,120499,77,120557,77,120615,77,120673,77,120731,77,1018,77,11416,77,5047,77,5616,77,5846,77,42207,77,66224,77,66321,77,119847,110,119899,110,119951,110,120003,110,120055,110,120107,110,120159,110,120211,110,120263,110,120315,110,120367,110,120419,110,120471,110,1400,110,1404,110,65326,78,8469,78,119821,78,119873,78,119925,78,119977,78,120029,78,120081,78,120185,78,120237,78,120289,78,120341,78,120393,78,120445,78,925,78,120500,78,120558,78,120616,78,120674,78,120732,78,11418,78,42208,78,66835,78,3074,111,3202,111,3330,111,3458,111,2406,111,2662,111,2790,111,3046,111,3174,111,3302,111,3430,111,3664,111,3792,111,4160,111,1637,111,1781,111,65359,111,8500,111,119848,111,119900,111,119952,111,120056,111,120108,111,120160,111,120212,111,120264,111,120316,111,120368,111,120420,111,120472,111,7439,111,7441,111,43837,111,959,111,120528,111,120586,111,120644,111,120702,111,120760,111,963,111,120532,111,120590,111,120648,111,120706,111,120764,111,11423,111,4351,111,1413,111,1505,111,1607,111,126500,111,126564,111,126596,111,65259,111,65260,111,65258,111,65257,111,1726,111,64428,111,64429,111,64427,111,64426,111,1729,111,64424,111,64425,111,64423,111,64422,111,1749,111,3360,111,4125,111,66794,111,71880,111,71895,111,66604,111,1984,79,2534,79,2918,79,12295,79,70864,79,71904,79,120782,79,120792,79,120802,79,120812,79,120822,79,130032,79,65327,79,119822,79,119874,79,119926,79,119978,79,120030,79,120082,79,120134,79,120186,79,120238,79,120290,79,120342,79,120394,79,120446,79,927,79,120502,79,120560,79,120618,79,120676,79,120734,79,11422,79,1365,79,11604,79,4816,79,2848,79,66754,79,42227,79,71861,79,66194,79,66219,79,66564,79,66838,79,9076,112,65360,112,119849,112,119901,112,119953,112,120005,112,120057,112,120109,112,120161,112,120213,112,120265,112,120317,112,120369,112,120421,112,120473,112,961,112,120530,112,120544,112,120588,112,120602,112,120646,112,120660,112,120704,112,120718,112,120762,112,120776,112,11427,112,65328,80,8473,80,119823,80,119875,80,119927,80,119979,80,120031,80,120083,80,120187,80,120239,80,120291,80,120343,80,120395,80,120447,80,929,80,120504,80,120562,80,120620,80,120678,80,120736,80,11426,80,5090,80,5229,80,42193,80,66197,80,119850,113,119902,113,119954,113,120006,113,120058,113,120110,113,120162,113,120214,113,120266,113,120318,113,120370,113,120422,113,120474,113,1307,113,1379,113,1382,113,8474,81,119824,81,119876,81,119928,81,119980,81,120032,81,120084,81,120188,81,120240,81,120292,81,120344,81,120396,81,120448,81,11605,81,119851,114,119903,114,119955,114,120007,114,120059,114,120111,114,120163,114,120215,114,120267,114,120319,114,120371,114,120423,114,120475,114,43847,114,43848,114,7462,114,11397,114,43905,114,119318,82,8475,82,8476,82,8477,82,119825,82,119877,82,119929,82,120033,82,120189,82,120241,82,120293,82,120345,82,120397,82,120449,82,422,82,5025,82,5074,82,66740,82,5511,82,42211,82,94005,82,65363,115,119852,115,119904,115,119956,115,120008,115,120060,115,120112,115,120164,115,120216,115,120268,115,120320,115,120372,115,120424,115,120476,115,42801,115,445,115,1109,115,43946,115,71873,115,66632,115,65331,83,119826,83,119878,83,119930,83,119982,83,120034,83,120086,83,120138,83,120190,83,120242,83,120294,83,120346,83,120398,83,120450,83,1029,83,1359,83,5077,83,5082,83,42210,83,94010,83,66198,83,66592,83,119853,116,119905,116,119957,116,120009,116,120061,116,120113,116,120165,116,120217,116,120269,116,120321,116,120373,116,120425,116,120477,116,8868,84,10201,84,128872,84,65332,84,119827,84,119879,84,119931,84,119983,84,120035,84,120087,84,120139,84,120191,84,120243,84,120295,84,120347,84,120399,84,120451,84,932,84,120507,84,120565,84,120623,84,120681,84,120739,84,11430,84,5026,84,42196,84,93962,84,71868,84,66199,84,66225,84,66325,84,119854,117,119906,117,119958,117,120010,117,120062,117,120114,117,120166,117,120218,117,120270,117,120322,117,120374,117,120426,117,120478,117,42911,117,7452,117,43854,117,43858,117,651,117,965,117,120534,117,120592,117,120650,117,120708,117,120766,117,1405,117,66806,117,71896,117,8746,85,8899,85,119828,85,119880,85,119932,85,119984,85,120036,85,120088,85,120140,85,120192,85,120244,85,120296,85,120348,85,120400,85,120452,85,1357,85,4608,85,66766,85,5196,85,42228,85,94018,85,71864,85,8744,118,8897,118,65366,118,8564,118,119855,118,119907,118,119959,118,120011,118,120063,118,120115,118,120167,118,120219,118,120271,118,120323,118,120375,118,120427,118,120479,118,7456,118,957,118,120526,118,120584,118,120642,118,120700,118,120758,118,1141,118,1496,118,71430,118,43945,118,71872,118,119309,86,1639,86,1783,86,8548,86,119829,86,119881,86,119933,86,119985,86,120037,86,120089,86,120141,86,120193,86,120245,86,120297,86,120349,86,120401,86,120453,86,1140,86,11576,86,5081,86,5167,86,42719,86,42214,86,93960,86,71840,86,66845,86,623,119,119856,119,119908,119,119960,119,120012,119,120064,119,120116,119,120168,119,120220,119,120272,119,120324,119,120376,119,120428,119,120480,119,7457,119,1121,119,1309,119,1377,119,71434,119,71438,119,71439,119,43907,119,71919,87,71910,87,119830,87,119882,87,119934,87,119986,87,120038,87,120090,87,120142,87,120194,87,120246,87,120298,87,120350,87,120402,87,120454,87,1308,87,5043,87,5076,87,42218,87,5742,120,10539,120,10540,120,10799,120,65368,120,8569,120,119857,120,119909,120,119961,120,120013,120,120065,120,120117,120,120169,120,120221,120,120273,120,120325,120,120377,120,120429,120,120481,120,5441,120,5501,120,5741,88,9587,88,66338,88,71916,88,65336,88,8553,88,119831,88,119883,88,119935,88,119987,88,120039,88,120091,88,120143,88,120195,88,120247,88,120299,88,120351,88,120403,88,120455,88,42931,88,935,88,120510,88,120568,88,120626,88,120684,88,120742,88,11436,88,11613,88,5815,88,42219,88,66192,88,66228,88,66327,88,66855,88,611,121,7564,121,65369,121,119858,121,119910,121,119962,121,120014,121,120066,121,120118,121,120170,121,120222,121,120274,121,120326,121,120378,121,120430,121,120482,121,655,121,7935,121,43866,121,947,121,8509,121,120516,121,120574,121,120632,121,120690,121,120748,121,1199,121,4327,121,71900,121,65337,89,119832,89,119884,89,119936,89,119988,89,120040,89,120092,89,120144,89,120196,89,120248,89,120300,89,120352,89,120404,89,120456,89,933,89,978,89,120508,89,120566,89,120624,89,120682,89,120740,89,11432,89,1198,89,5033,89,5053,89,42220,89,94019,89,71844,89,66226,89,119859,122,119911,122,119963,122,120015,122,120067,122,120119,122,120171,122,120223,122,120275,122,120327,122,120379,122,120431,122,120483,122,7458,122,43923,122,71876,122,66293,90,71909,90,65338,90,8484,90,8488,90,119833,90,119885,90,119937,90,119989,90,120041,90,120197,90,120249,90,120301,90,120353,90,120405,90,120457,90,918,90,120493,90,120551,90,120609,90,120667,90,120725,90,5059,90,42204,90,71849,90,65282,34,65284,36,65285,37,65286,38,65290,42,65291,43,65294,46,65295,47,65296,48,65297,49,65298,50,65299,51,65300,52,65301,53,65302,54,65303,55,65304,56,65305,57,65308,60,65309,61,65310,62,65312,64,65316,68,65318,70,65319,71,65324,76,65329,81,65330,82,65333,85,65334,86,65335,87,65343,95,65346,98,65348,100,65350,102,65355,107,65357,109,65358,110,65361,113,65362,114,65364,116,65365,117,65367,119,65370,122,65371,123,65373,125],\"_default\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"cs\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"de\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"es\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"fr\":[65374,126,65306,58,65281,33,8216,96,8245,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"it\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ja\":[8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65292,44,65307,59],\"ko\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pl\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pt-BR\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"qps-ploc\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ru\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,305,105,921,73,1009,112,215,120,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"tr\":[160,32,8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"zh-hans\":[65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41],\"zh-hant\":[8211,45,65374,126,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65307,59]}' ); }); From 35c70dc0a26c557d909529fb6f5ac5544e59877e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 7 Jan 2022 15:14:07 +0100 Subject: [PATCH 1122/2210] minify svgs Related-to: #140159 --- build/lib/optimize.js | 4 +- build/lib/optimize.ts | 5 + package.json | 2 + .../gettingStarted/common/media/languages.svg | 2 +- yarn.lock | 116 +++++++++++++++++- 5 files changed, 125 insertions(+), 4 deletions(-) diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 931d442760cf6..d5b957d119521 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -177,8 +177,10 @@ function minifyTask(src, sourceMapBaseUrl) { const cssnano = require('cssnano'); const postcss = require('gulp-postcss'); const sourcemaps = require('gulp-sourcemaps'); + const svgmin = require('gulp-svgmin'); const jsFilter = filter('**/*.js', { restore: true }); const cssFilter = filter('**/*.css', { restore: true }); + const svgFilter = filter('**/*.svg', { restore: true }); pump(gulp.src([src + '/**', '!' + src + '/**/*.map']), jsFilter, sourcemaps.init({ loadMaps: true }), es.map((f, cb) => { esbuild.build({ entryPoints: [f.path], @@ -195,7 +197,7 @@ function minifyTask(src, sourceMapBaseUrl) { f.sourceMap = JSON.parse(sourceMapFile.text); cb(undefined, f); }, cb); - }), jsFilter.restore, cssFilter, postcss([cssnano({ preset: 'default' })]), cssFilter.restore, sourcemaps.mapSources((sourcePath) => { + }), jsFilter.restore, cssFilter, postcss([cssnano({ preset: 'default' })]), cssFilter.restore, svgFilter, svgmin(), svgFilter.restore, sourcemaps.mapSources((sourcePath) => { if (sourcePath === 'bootstrap-fork.js') { return 'bootstrap-fork.orig.js'; } diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 96b4e854f6f1a..fc2a2f4266191 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -252,9 +252,11 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => const cssnano = require('cssnano') as typeof import('cssnano'); const postcss = require('gulp-postcss') as typeof import('gulp-postcss'); const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); + const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin'); const jsFilter = filter('**/*.js', { restore: true }); const cssFilter = filter('**/*.css', { restore: true }); + const svgFilter = filter('**/*.svg', { restore: true }); pump( gulp.src([src + '/**', '!' + src + '/**/*.map']), @@ -283,6 +285,9 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => cssFilter, postcss([cssnano({ preset: 'default' })]), cssFilter.restore, + svgFilter, + svgmin(), + svgFilter.restore, (sourcemaps).mapSources((sourcePath: string) => { if (sourcePath === 'bootstrap-fork.js') { return 'bootstrap-fork.orig.js'; diff --git a/package.json b/package.json index 883ae44cf6825..8dee0ee60a6c8 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "@types/debug": "4.1.5", "@types/graceful-fs": "4.1.2", "@types/gulp-postcss": "^8.0.0", + "@types/gulp-svgmin": "^1.2.1", "@types/http-proxy-agent": "^2.0.1", "@types/keytar": "^4.4.0", "@types/minimist": "^1.2.1", @@ -159,6 +160,7 @@ "gulp-replace": "^0.5.4", "gulp-shell": "^0.6.5", "gulp-sourcemaps": "^3.0.0", + "gulp-svgmin": "^4.1.0", "gulp-tsb": "4.0.6", "gulp-untar": "^0.0.7", "gulp-vinyl-zip": "^2.1.2", diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/languages.svg b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/languages.svg index 5d719827f4e30..820ead93c1b63 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/media/languages.svg +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/media/languages.svg @@ -1,4 +1,4 @@ - + diff --git a/yarn.lock b/yarn.lock index 3fa67b16d172c..07d71e943be87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -563,6 +563,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + "@ts-morph/common@~0.11.1": version "0.11.1" resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.11.1.tgz#281af2a0642b19354d8aa07a0d50dfdb4aa8164e" @@ -680,6 +685,15 @@ "@types/node" "*" "@types/vinyl" "*" +"@types/gulp-svgmin@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/gulp-svgmin/-/gulp-svgmin-1.2.1.tgz#e18f344ea09560554652406b37e1dc3253a6bda2" + integrity sha512-qT/Y+C2uWJZoGw4oAjuJGZk+ImmTrx+QZbMGSzf8a1absW3wztrmMPvCF64pdogATDVUSPQDLzPWAFeIxylJTA== + dependencies: + "@types/node" "*" + "@types/svgo" "^1" + "@types/vinyl" "*" + "@types/http-proxy-agent@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.1.tgz#2f95077f6bfe7adc39cc0f0042da85997ae77fc7" @@ -779,6 +793,11 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== +"@types/svgo@^1": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@types/svgo/-/svgo-1.3.6.tgz#9db00a7ddf9b26ad2feb6b834bef1818677845e1" + integrity sha512-AZU7vQcy/4WFEuwnwsNsJnFwupIpbllH1++LXScN6uxT1Z4zPzdrWG97w4/I7eFKFTvfy/bHFStWjdBAg2Vjug== + "@types/tapable@*": version "1.0.4" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" @@ -2607,7 +2626,7 @@ commander@^5.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^7.0.0: +commander@^7.0.0, commander@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== @@ -2885,6 +2904,17 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" +css-select@^4.1.3: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== + dependencies: + boolbase "^1.0.0" + css-what "^5.1.0" + domhandler "^4.3.0" + domutils "^2.8.0" + nth-check "^2.0.1" + css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" @@ -2901,11 +2931,24 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== +css-what@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + css@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" @@ -2988,7 +3031,7 @@ cssnano@^4.1.11: is-resolvable "^1.0.0" postcss "^7.0.0" -csso@^4.0.2: +csso@^4.0.2, csso@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== @@ -3287,6 +3330,15 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -3302,6 +3354,18 @@ domelementtype@^2.0.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== +domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domhandler@^4.2.0, domhandler@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + dependencies: + domelementtype "^2.2.0" + domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -3310,6 +3374,15 @@ domutils@^1.7.0: dom-serializer "0" domelementtype "1" +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -4970,6 +5043,15 @@ gulp-sourcemaps@^3.0.0: strip-bom-string "^1.0.0" through2 "^2.0.0" +gulp-svgmin@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/gulp-svgmin/-/gulp-svgmin-4.1.0.tgz#38c7928663b036a676c56e78cc62fd3f906a0133" + integrity sha512-WKpif+yu3+oIlp1e11CQi5F64YddP699l2mFmxpz8swv8/P8dhxVcMKdCPFWouArlVyn7Ma1eWCJHw5gx4NMtw== + dependencies: + lodash.clonedeep "^4.5.0" + plugin-error "^1.0.1" + svgo "^2.7.0" + gulp-symdest@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gulp-symdest/-/gulp-symdest-1.2.0.tgz#bda30559a7a65ff3a31038ba539c63ca4c2d3259" @@ -6265,6 +6347,11 @@ lodash.clone@^4.3.2: resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -7144,6 +7231,13 @@ nth-check@^1.0.2: dependencies: boolbase "~1.0.0" +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -7635,6 +7729,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" @@ -9587,6 +9686,19 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" +svgo@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" From a9acf06479370215a716c593ce615cfdd6b81502 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 7 Jan 2022 15:18:18 +0100 Subject: [PATCH 1123/2210] fix webignore patterns for appinsights also, web: look for minified entrypoints as well Closes: #140158 --- build/.webignore | 4 ++++ build/lib/util.js | 11 +++++++++-- build/lib/util.ts | 20 +++++++++++++++++--- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/build/.webignore b/build/.webignore index fc1d2067643a5..f55882ba9d446 100644 --- a/build/.webignore +++ b/build/.webignore @@ -32,3 +32,7 @@ xterm-addon-webgl/out/** # This makes sure the model is included in the package !@vscode/vscode-languagedetection/model/** + +@microsoft/applicationinsights*/** +@microsoft/dynamicproto-js/** +!@microsoft/applicationinsights-web/dist/applicationinsights-web.min.js \ No newline at end of file diff --git a/build/lib/util.js b/build/lib/util.js index 9effd6a0f22ef..e1fbc80f5d8a2 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -323,10 +323,17 @@ function acquireWebNodePaths() { } // Remove any starting path information so it's all relative info if (entryPoint.startsWith('./')) { - entryPoint = entryPoint.substr(2); + entryPoint = entryPoint.substring(2); } else if (entryPoint.startsWith('/')) { - entryPoint = entryPoint.substr(1); + entryPoint = entryPoint.substring(1); + } + // Search for a minified entrypoint as well + if (/(? Date: Fri, 7 Jan 2022 06:18:40 -0800 Subject: [PATCH 1124/2210] Bump copy-props from 2.0.4 to 2.0.5 (#140238) Bumps [copy-props](https://github.com/gulpjs/copy-props) from 2.0.4 to 2.0.5. - [Release notes](https://github.com/gulpjs/copy-props/releases) - [Changelog](https://github.com/gulpjs/copy-props/blob/master/CHANGELOG.md) - [Commits](https://github.com/gulpjs/copy-props/compare/2.0.4...2.0.5) --- updated-dependencies: - dependency-name: copy-props dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 07d71e943be87..fffdb40c9d03d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2734,12 +2734,12 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= copy-props@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" - integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + version "2.0.5" + resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.5.tgz#03cf9ae328d4ebb36f8f1d804448a6af9ee3f2d2" + integrity sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw== dependencies: - each-props "^1.3.0" - is-plain-object "^2.0.1" + each-props "^1.3.2" + is-plain-object "^5.0.0" copy-webpack-plugin@^6.0.3: version "6.4.1" @@ -3410,7 +3410,7 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -each-props@^1.3.0: +each-props@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== From 6c78a84e83230384eeecfb007de2a6528087bd00 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 7 Jan 2022 10:44:53 +0100 Subject: [PATCH 1125/2210] Show remote cli connection error: For vscode-remote-release#6136 --- src/vs/server/remoteCli.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/server/remoteCli.ts b/src/vs/server/remoteCli.ts index 993184d89bc87..0a0856a3b760b 100644 --- a/src/vs/server/remoteCli.ts +++ b/src/vs/server/remoteCli.ts @@ -356,13 +356,13 @@ function sendToPipe(args: PipeCommand, verbose: boolean): Promise { res.on('data', chunk => { chunks.push(chunk); }); - res.on('error', () => fatal('Error in response')); + res.on('error', (err) => fatal('Error in response.', err)); res.on('end', () => { resolve(chunks.join('')); }); }); - req.on('error', () => fatal('Error in request')); + req.on('error', (err) => fatal('Error in request.', err)); req.write(message); req.end(); }); @@ -372,8 +372,8 @@ function asExtensionIdOrVSIX(inputs: string[] | undefined) { return inputs?.map(input => /\.vsix$/i.test(input) ? pathToURI(input).href : input); } -function fatal(err: any): void { - console.error('Unable to connect to VS Code server.'); +function fatal(message: string, err: any): void { + console.error('Unable to connect to VS Code server: ' + message); console.error(err); process.exit(1); } From 873a89520832e972db65da53da8cbfe8cc44a355 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 7 Jan 2022 14:32:20 +0100 Subject: [PATCH 1126/2210] Update request-light. Fixes #140227 --- extensions/json-language-features/package.json | 14 ++++++++------ .../json-language-features/server/package.json | 2 +- extensions/json-language-features/server/yarn.lock | 8 ++++---- extensions/json-language-features/yarn.lock | 8 ++++---- extensions/npm/package.json | 2 +- extensions/npm/yarn.lock | 8 ++++---- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 59b59300c0210..6e87db294bb56 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -135,14 +135,16 @@ "url": "http://json-schema.org/draft-07/schema#" } ], - "commands": [{ - "command": "json.clearCache", - "title": "%json.command.clearCache%", - "category": "JSON" - }] + "commands": [ + { + "command": "json.clearCache", + "title": "%json.command.clearCache%", + "category": "JSON" + } + ] }, "dependencies": { - "request-light": "^0.5.5", + "request-light": "^0.5.7", "vscode-extension-telemetry": "0.4.4", "vscode-languageclient": "^7.0.0", "vscode-nls": "^5.0.0" diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 2cbc5e4459cc6..77f01729428e0 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -13,7 +13,7 @@ "main": "./out/node/jsonServerMain", "dependencies": { "jsonc-parser": "^3.0.0", - "request-light": "^0.5.5", + "request-light": "^0.5.7", "vscode-json-languageservice": "^4.2.0-next.2", "vscode-languageserver": "^7.0.0", "vscode-uri": "^3.0.2" diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 2c9e39937d09e..1d1a3e5346040 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -17,10 +17,10 @@ jsonc-parser@^3.0.0: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== -request-light@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.5.tgz#254ab0b38a1db2192170b599b05181934e14932b" - integrity sha512-AvjfJuhyT6dYfhtIBF+IpTPQco+Td1QJ6PsIJ5xui110vQ5p9HxHk+m1XJqXazLQT6CxxSx9eNv6R/+fu4bZig== +request-light@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.7.tgz#1c448c22153b55d2cd278eb414df24a5ad6e6d5e" + integrity sha512-i/wKzvcx7Er8tZnvqSxWuNO5ZGggu2UgZAqj/RyZ0si7lBTXL7kZiI/dWxzxnQjaY7s5HEy1qK21Do4Ncr6cVw== vscode-json-languageservice@^4.2.0-next.2: version "4.2.0-next.2" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 34ba725357845..dd68c87d0a5df 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -39,10 +39,10 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -request-light@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.5.tgz#254ab0b38a1db2192170b599b05181934e14932b" - integrity sha512-AvjfJuhyT6dYfhtIBF+IpTPQco+Td1QJ6PsIJ5xui110vQ5p9HxHk+m1XJqXazLQT6CxxSx9eNv6R/+fu4bZig== +request-light@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.7.tgz#1c448c22153b55d2cd278eb414df24a5ad6e6d5e" + integrity sha512-i/wKzvcx7Er8tZnvqSxWuNO5ZGggu2UgZAqj/RyZ0si7lBTXL7kZiI/dWxzxnQjaY7s5HEy1qK21Do4Ncr6cVw== semver@^7.3.4: version "7.3.4" diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 8c4bc7ba0717d..b674260dd5bb9 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -21,7 +21,7 @@ "find-yarn-workspace-root": "^2.0.0", "jsonc-parser": "^2.2.1", "minimatch": "^3.0.4", - "request-light": "^0.5.5", + "request-light": "^0.5.7", "vscode-nls": "^5.0.0", "which": "^2.0.2", "which-pm": "^2.0.0" diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index fef65191b4baf..4728dbc860c2a 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -170,10 +170,10 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -request-light@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.5.tgz#254ab0b38a1db2192170b599b05181934e14932b" - integrity sha512-AvjfJuhyT6dYfhtIBF+IpTPQco+Td1QJ6PsIJ5xui110vQ5p9HxHk+m1XJqXazLQT6CxxSx9eNv6R/+fu4bZig== +request-light@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.7.tgz#1c448c22153b55d2cd278eb414df24a5ad6e6d5e" + integrity sha512-i/wKzvcx7Er8tZnvqSxWuNO5ZGggu2UgZAqj/RyZ0si7lBTXL7kZiI/dWxzxnQjaY7s5HEy1qK21Do4Ncr6cVw== sprintf-js@~1.0.2: version "1.0.3" From 221ac5cee35b97db211bf9aaa03961d6260c4970 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 7 Jan 2022 15:19:33 +0100 Subject: [PATCH 1127/2210] more server cli descriptions, and port simplification --- src/vs/server/main.js | 86 +++++++++++++---------- src/vs/server/serverEnvironmentService.ts | 59 ++++++++++------ 2 files changed, 87 insertions(+), 58 deletions(-) diff --git a/src/vs/server/main.js b/src/vs/server/main.js index d1e27d138e97e..3f344898fed64 100644 --- a/src/vs/server/main.js +++ b/src/vs/server/main.js @@ -9,6 +9,7 @@ const perf = require('../base/common/performance'); const performance = require('perf_hooks').performance; const product = require('../../../product.json'); const readline = require('readline'); +const http = require('http'); perf.mark('code/server/start'); // @ts-ignore @@ -159,67 +160,76 @@ async function start() { * @throws */ async function parsePort(strPort, strPickPort) { - let specificPort = -1; - + let specificPort; if (strPort) { - const port = parseInt(strPort, 10); - if (!isNaN(port)) { - specificPort = port; + let range; + if (strPort.match(/^\d+$/)) { + specificPort = parseInt(strPort, 10); + if (specificPort === 0 || !strPickPort) { + return specificPort; + } + } else if (range = parseRange(strPort)) { + return await findFreePort(range.start, range.end, 8000); } else { - console.log('Port is not a number, will default to 8000 if no pick-port is given.'); + console.log('--port "${strPort}" is not a valid number or range.'); } } - if (strPickPort) { - if (strPickPort.match(/^\d+-\d+$/)) { - const [start, end] = strPickPort.split('-').map(numStr => { return parseInt(numStr, 10); }); - - if (!isNaN(start) && !isNaN(end)) { - if (specificPort !== -1 && specificPort >= start && specificPort <= end) { - return specificPort; - } else { - return await findFreePort(start, start, end); - } + const range = parseRange(strPickPort); + if (range) { + if (range.start <= specificPort && specificPort <= range.end) { + return specificPort; } else { - console.log('Port range are not numbers, using 8000 instead.'); + return await findFreePort(range.start, range.end, 8000); } } else { - console.log(`Port range: "${strPickPort}" is not properly formatted, using 8000 instead.`); + console.log(`--pick-port "${strPickPort}" is not properly formatted.`); } } + return 8000; +} - if (specificPort !== -1) { - return specificPort; +/** + * @param {string} strRange + * @returns {{ start: number; end: number } | undefined} + */ +function parseRange(strRange) { + const match = strRange.match(/^(\d+)-(\d+)$/); + if (match) { + return { start: parseInt(match[1], 10), end: parseInt(match[2], 10) }; } - - return 8000; + return undefined; } /** * Starting at the `start` port, look for a free port incrementing - * by 1 until `end` inclusive. If no port is found error is thrown. + * by 1 until `end` inclusive. If no free port is found, the fallback is returned. * * @param {number} start - * @param {number} port * @param {number} end + * @param {number} fallback * @returns {Promise} * @throws */ -async function findFreePort(start, port, end) { - const http = require('http'); - return new Promise((resolve, reject) => { - if (port > end) { - throw new Error(`Could not find free port in range: ${start}-${end}`); - } - - const server = http.createServer(); - server.listen(port, () => { - server.close(); - resolve(port); - }).on('error', () => { - resolve(findFreePort(start, port + 1, end)); +async function findFreePort(start, end, fallback) { + const testPort = (port) => { + return new Promise((resolve) => { + const server = http.createServer(); + server.listen(port, '127.0.0.1', () => { + server.close(); + resolve(true); + }).on('error', () => { + resolve(false); + }); }); - }); + }; + for (let port = start; port < end; port++) { + if (await testPort(port)) { + return port; + } + } + console.log(`Could not find free port in range: ${start}-${end}. Using ${fallback} instead.`); + return fallback; } /** @returns { Promise } */ diff --git a/src/vs/server/serverEnvironmentService.ts b/src/vs/server/serverEnvironmentService.ts index aaebbd4922f0c..a592f44381da6 100644 --- a/src/vs/server/serverEnvironmentService.ts +++ b/src/vs/server/serverEnvironmentService.ts @@ -4,58 +4,77 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; + import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { OPTIONS, OptionDescriptions } from 'vs/platform/environment/node/argv'; import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; export const serverOptions: OptionDescriptions = { - 'port': { type: 'string' }, + + /* ----- server setup ----- */ + + 'host': { type: 'string', cat: 'o', description: nls.localize('host', 'The IP address the server should listen to. To use in combination with port.') }, + 'port': { type: 'string', cat: 'o', description: nls.localize('port', 'The port the server should listen to. If 0 is passed a random free port is picked. If a range in the format num-num is passed, a free port from the range is selected.') }, 'pick-port': { type: 'string' }, + 'socket-path': { type: 'string', cat: 'o', description: nls.localize('socket-path', 'The path to a socket file for the server to listen to.') }, 'connection-token': { type: 'string', cat: 'o', deprecates: 'connectionToken', description: nls.localize('connection-token', "A secret that must be included by the web client with all requests.") }, 'connection-secret': { type: 'string', cat: 'o', description: nls.localize('connection-secret', "Path to file that contains the connection token. This will require that all incoming connections know the secret.") }, - 'host': { type: 'string' }, - 'socket-path': { type: 'string' }, - 'driver': { type: 'string' }, - 'start-server': { type: 'boolean' }, + 'disable-websocket-compression': { type: 'boolean' }, 'print-startup-performance': { type: 'boolean' }, 'print-ip-address': { type: 'boolean' }, - 'disable-websocket-compression': { type: 'boolean' }, + 'accept-server-license-terms': { type: 'boolean', cat: 'o', description: nls.localize('acceptLicenseTerms', 'If set, the user accepts the server license terms and the server will be started without a user prompt.') }, + + /* ----- vs code options ----- */ + 'user-data-dir': OPTIONS['user-data-dir'], + 'driver': OPTIONS['driver'], + 'disable-telemetry': OPTIONS['disable-telemetry'], 'fileWatcherPolling': { type: 'string' }, + 'log': OPTIONS['log'], + 'logsPath': OPTIONS['logsPath'], + 'force-disable-user-env': OPTIONS['force-disable-user-env'], - 'enable-remote-auto-shutdown': { type: 'boolean' }, - 'remote-auto-shutdown-without-delay': { type: 'boolean' }, + /* ----- vs code web options ----- */ - 'without-browser-env-var': { type: 'boolean' }, + 'folder': { type: 'string' }, + 'workspace': { type: 'string' }, - 'disable-telemetry': OPTIONS['disable-telemetry'], + 'enable-sync': { type: 'boolean' }, + 'github-auth': { type: 'string' }, + + /* ----- extension management ----- */ 'extensions-dir': OPTIONS['extensions-dir'], 'extensions-download-dir': OPTIONS['extensions-download-dir'], + 'builtin-extensions-dir': OPTIONS['builtin-extensions-dir'], 'install-extension': OPTIONS['install-extension'], 'install-builtin-extension': OPTIONS['install-builtin-extension'], 'uninstall-extension': OPTIONS['uninstall-extension'], - 'locate-extension': OPTIONS['locate-extension'], 'list-extensions': OPTIONS['list-extensions'], - 'force': OPTIONS['force'], + 'locate-extension': OPTIONS['locate-extension'], + 'show-versions': OPTIONS['show-versions'], 'category': OPTIONS['category'], + 'force': OPTIONS['force'], 'do-not-sync': OPTIONS['do-not-sync'], + 'pre-release': OPTIONS['pre-release'], + 'start-server': { type: 'boolean' }, - 'force-disable-user-env': OPTIONS['force-disable-user-env'], - 'folder': { type: 'string' }, - 'workspace': { type: 'string' }, + /* ----- remote development options ----- */ + + 'enable-remote-auto-shutdown': { type: 'boolean' }, + 'remote-auto-shutdown-without-delay': { type: 'boolean' }, + 'use-host-proxy': { type: 'string' }, - 'enable-sync': { type: 'boolean' }, - 'github-auth': { type: 'string' }, - 'log': { type: 'string' }, - 'logsPath': { type: 'string' }, + 'without-browser-env-var': { type: 'boolean' }, + + /* ----- server cli ----- */ 'help': OPTIONS['help'], 'version': OPTIONS['version'], - 'accept-server-license-terms': { type: 'boolean' }, + _: OPTIONS['_'] }; From c8ab575e4ee5789b37d525afb5278a432e770419 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 7 Jan 2022 08:46:48 -0600 Subject: [PATCH 1128/2210] improve handling of cwds --- .../terminal/browser/terminalInstance.ts | 21 +++---------- .../browser/xterm/commandTrackerAddon.ts | 30 +++++++++++++++---- .../contrib/terminal/common/terminal.ts | 3 +- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 2111cf27ab678..be1704e2ff262 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -699,7 +699,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } async runRecent(type: 'command' | 'cwd'): Promise { - const commands = this.xterm?.commandTracker.getCommands(); + const commands = this.xterm?.commandTracker.commands; if (!commands || !this.xterm) { return; } @@ -722,22 +722,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); } } else { - const cwds = this.xterm.commandTracker.getCommands().map(c => c.cwd).filter(c => c !== undefined && c !== this.cwd); - const map = new Map(); - if (!cwds) { - return; - } - for (const cwd of cwds) { - const entry = map.get(cwd!); - if (entry) { - map.set(cwd!, entry + 1); - } else { - map.set(cwd!, 1); - } - } - const sorted = [...map.entries()].sort((a, b) => b[1] - a[1]); - for (const entry of sorted) { - items.push({ label: entry[0] }); + const cwds = this.xterm.commandTracker.cwds; + for (const label of cwds) { + items.push({ label }); } } const result = await this._quickInputService.pick(items.reverse(), {}); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index d16f50405d6c6..8e15e11c965e0 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -29,7 +29,8 @@ export abstract class CommandTrackerAddon implements ICommandTracker, ITerminalA private _isDisposable: boolean = false; abstract _terminal: Terminal | undefined; - abstract getCommands(): TerminalCommand[]; + abstract get commands(): TerminalCommand[]; + abstract get cwds(): string[]; abstract activate(terminal: Terminal): void; abstract handleIntegratedShellChange(event: { type: string, value: string }): void; @@ -313,7 +314,10 @@ export abstract class CommandTrackerAddon implements ICommandTracker, ITerminalA export class NaiveCommandTrackerAddon extends CommandTrackerAddon { _terminal: Terminal | undefined; - getCommands(): TerminalCommand[] { + get commands(): TerminalCommand[] { + return []; + } + get cwds(): string[] { return []; } @@ -346,6 +350,7 @@ export class NaiveCommandTrackerAddon extends CommandTrackerAddon { export class CognisantCommandTrackerAddon extends CommandTrackerAddon { _terminal: Terminal | undefined; private _commands: TerminalCommand[] = []; + private _cwds = new Map(); private _exitCode: number | undefined; private _cwd: string | undefined; private _commandMarker: IMarker | undefined; @@ -362,11 +367,17 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { return; } switch (event.type) { - case ShellIntegrationInfo.CurrentDir: + case ShellIntegrationInfo.CurrentDir: { this._cwd = event.value; + const freq = this._cwds.get(this._cwd); + if (freq) { + this._cwds.set(this._cwd, freq + 1); + } else { + this._cwds.set(this._cwd, 1); + } this._onCwdChanged.fire(this._cwd); break; - case ShellIntegrationInteraction.PromptStart: + } case ShellIntegrationInteraction.PromptStart: break; case ShellIntegrationInteraction.CommandStart: this._commandMarker = this._terminal.registerMarker(0); @@ -396,7 +407,16 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { } } - getCommands(): TerminalCommand[] { + get commands(): TerminalCommand[] { return this._commands; } + + get cwds(): string[] { + const cwds = []; + const sorted = new Map([...this._cwds.entries()].sort((a, b) => b[1] - a[1])); + for (const [key,] of sorted.entries()) { + cwds.push(key); + } + return cwds; + } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 9be3f9c7c112e..38c007a2d9d4c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -326,7 +326,8 @@ export interface ICommandTracker { selectToNextCommand(): void; selectToPreviousLine(): void; selectToNextLine(): void; - getCommands(): TerminalCommand[]; + get commands(): TerminalCommand[]; + get cwds(): string[]; clearMarker(): void; } From 2ecbdc8c56eeefafb5e48704a3a3052a30d8991c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 07:16:02 -0800 Subject: [PATCH 1129/2210] Trim optional line endings correctly --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index be1704e2ff262..3c07008a32e3d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -707,8 +707,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const items: Item[] = []; if (type === 'command') { for (const { command, timestamp, cwd, exitCode } of commands) { - // trim off /r - const label = command.substring(0, command.length - 1); + // trim off any whitespace and/or line endings + const label = command.trim(); if (label.length === 0) { continue; } From c1101ed2d4f957e6092a320b964664eda9800c23 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 07:18:51 -0800 Subject: [PATCH 1130/2210] Get basic Windows shell integration mostly working --- .../xterm/cognisantCommandTrackerAddon.ts | 142 ++++++++++++++++++ .../browser/xterm/commandTrackerAddon.ts | 79 +--------- .../terminal/browser/xterm/xtermTerminal.ts | 7 +- 3 files changed, 149 insertions(+), 79 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts new file mode 100644 index 0000000000000..2add71d31a9df --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { Terminal, IMarker } from 'xterm'; +import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; +import { Emitter } from 'vs/base/common/event'; +import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; +import { ILogService } from 'vs/platform/log/common/log'; + +interface ICurrentPartialCommand { + marker?: IMarker; + previousCommandMarker?: IMarker; + promptStartY?: number; + commandStartY?: number; + commandStartX?: number; + commandExecutedY?: number; + commandFinishedY?: number; + command?: string; +} + +export class CognisantCommandTrackerAddon extends CommandTrackerAddon { + private _commands: TerminalCommand[] = []; + private _cwds = new Map(); + private _exitCode: number | undefined; + private _cwd: string | undefined; + private _currentCommand: ICurrentPartialCommand = {}; + + protected _terminal: Terminal | undefined; + + private readonly _onCwdChanged = new Emitter(); + readonly onCwdChanged = this._onCwdChanged.event; + + constructor( + @ILogService private readonly _logService: ILogService + ) { + super(); + } + + activate(terminal: Terminal): void { + this._terminal = terminal; + } + + handleIntegratedShellChange(event: { type: string, value: string }): void { + if (!this._terminal) { + return; + } + switch (event.type) { + case ShellIntegrationInfo.CurrentDir: { + this._cwd = event.value; + const freq = this._cwds.get(this._cwd); + if (freq) { + this._cwds.set(this._cwd, freq + 1); + } else { + this._cwds.set(this._cwd, 1); + } + this._onCwdChanged.fire(this._cwd); + break; + } + case ShellIntegrationInteraction.PromptStart: + this._currentCommand.promptStartY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY; + break; + case ShellIntegrationInteraction.CommandStart: + this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX; + this._currentCommand.commandStartY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY; + this._currentCommand.marker = this._terminal.registerMarker(0); + break; + case ShellIntegrationInteraction.CommandExecuted: + this._currentCommand.commandExecutedY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY; + + // TODO: Only do on this on Windows backends + // Check if the command line is the same as the previous command line or if the + // start Y differs from the executed Y. This is to catch the conpty case where the + // "rendering" of the shell integration sequences doesn't occur on the correct cell + // due to https://github.com/microsoft/terminal/issues/11220 + if (this._currentCommand.previousCommandMarker?.line === this._currentCommand.marker?.line || + this._currentCommand.commandStartY === this._currentCommand.commandExecutedY) { + this._currentCommand.marker = this._terminal?.registerMarker(0); + this._currentCommand.commandStartX = 0; + } + + // TODO: This does not yet work when the prompt line is wrapped + this._currentCommand.command = this._terminal!.buffer.active.getLine(this._currentCommand.commandExecutedY)?.translateToString(true, this._currentCommand.commandStartX || 0); + + // TODO: Only do on this on Windows backends + // Something went wrong, try predict the prompt based on the shell. + if (this._currentCommand.commandStartX === 0) { + // TODO: Only do this on pwsh + const promptPredictions = [ + `PS ${this._cwd}> `, + `PS>`, + ]; + for (const promptPrediction of promptPredictions) { + if (this._currentCommand.command?.startsWith(promptPrediction)) { + // TODO: Consider cell vs string positioning; test CJK + this._currentCommand.commandStartX = promptPrediction.length; + this._currentCommand.command = this._currentCommand.command.substring(this._currentCommand.commandStartX); + break; + } + } + } + break; + case ShellIntegrationInteraction.CommandFinished: + this._logService.trace('Terminal Command Finished', this._currentCommand.command); + this._exitCode = Number.parseInt(event.value); + if (!this._currentCommand.marker?.line || !this._terminal.buffer.active) { + break; + } + if (this._currentCommand.command && !this._currentCommand.command.startsWith('\\') && this._currentCommand.command !== '') { + this._commands.push({ + command: this._currentCommand.command, + // TODO: Date.now() is equivalent? + timestamp: new Date().getTime(), + cwd: this._cwd, + exitCode: this._exitCode + }); + } + + // TODO: Dispose + // this._currentCommand.previousCommandMarker?.dispose(); + this._currentCommand.previousCommandMarker = this._currentCommand.marker; + this._currentCommand.marker = undefined; + break; + default: + return; + } + } + + get commands(): TerminalCommand[] { + return this._commands; + } + + get cwds(): string[] { + const cwds = []; + const sorted = new Map([...this._cwds.entries()].sort((a, b) => b[1] - a[1])); + for (const [key,] of sorted.entries()) { + cwds.push(key); + } + return cwds; + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts index 8e15e11c965e0..120a7db0d767a 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon.ts @@ -5,8 +5,7 @@ import type { Terminal, IMarker, ITerminalAddon } from 'xterm'; import { ICommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; -import { Emitter } from 'vs/base/common/event'; +import { TerminalCommand } from 'vs/platform/terminal/common/terminal'; /** * The minimum size of the prompt in which to assume the line is a command. @@ -27,7 +26,7 @@ export abstract class CommandTrackerAddon implements ICommandTracker, ITerminalA private _currentMarker: IMarker | Boundary = Boundary.Bottom; private _selectionStart: IMarker | Boundary | null = null; private _isDisposable: boolean = false; - abstract _terminal: Terminal | undefined; + protected abstract _terminal: Terminal | undefined; abstract get commands(): TerminalCommand[]; abstract get cwds(): string[]; @@ -346,77 +345,3 @@ export class NaiveCommandTrackerAddon extends CommandTrackerAddon { handleIntegratedShellChange(event: { type: string; value: string; }): void { } } - -export class CognisantCommandTrackerAddon extends CommandTrackerAddon { - _terminal: Terminal | undefined; - private _commands: TerminalCommand[] = []; - private _cwds = new Map(); - private _exitCode: number | undefined; - private _cwd: string | undefined; - private _commandMarker: IMarker | undefined; - private _commandCharStart: number | undefined; - private readonly _onCwdChanged = new Emitter(); - readonly onCwdChanged = this._onCwdChanged.event; - - activate(terminal: Terminal): void { - this._terminal = terminal; - } - - handleIntegratedShellChange(event: { type: string, value: string }): void { - if (!this._terminal) { - return; - } - switch (event.type) { - case ShellIntegrationInfo.CurrentDir: { - this._cwd = event.value; - const freq = this._cwds.get(this._cwd); - if (freq) { - this._cwds.set(this._cwd, freq + 1); - } else { - this._cwds.set(this._cwd, 1); - } - this._onCwdChanged.fire(this._cwd); - break; - } case ShellIntegrationInteraction.PromptStart: - break; - case ShellIntegrationInteraction.CommandStart: - this._commandMarker = this._terminal.registerMarker(0); - this._commandCharStart = this._terminal.buffer.active.cursorX; - break; - case ShellIntegrationInteraction.CommandExecuted: - break; - case ShellIntegrationInteraction.CommandFinished: { - this._exitCode = Number.parseInt(event.value); - if (!this._commandMarker?.line || !this._terminal.buffer.active) { - break; - } - const command = this._terminal.buffer.active.getLine(this._commandMarker.line)?.translateToString().substring(this._commandCharStart || 0); - if (command && !command.startsWith('\\') && command !== '') { - this._commands.push( - { - command, - timestamp: new Date().getTime(), - cwd: this._cwd, - exitCode: this._exitCode - }); - } - break; - } - default: - return; - } - } - - get commands(): TerminalCommand[] { - return this._commands; - } - - get cwds(): string[] { - const cwds = []; - const sorted = new Map([...this._cwds.entries()].sort((a, b) => b[1] - a[1])); - for (const [key,] of sorted.entries()) { - cwds.push(key); - } - return cwds; - } -} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 68a712373ad0c..175c1e2294145 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -20,7 +20,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { CognisantCommandTrackerAddon, CommandTrackerAddon, NaiveCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; +import { CommandTrackerAddon, NaiveCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { localize } from 'vs/nls'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; @@ -29,6 +29,8 @@ import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme import { TERMINAL_FOREGROUND_COLOR, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR, ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { Color } from 'vs/base/common/color'; import { ShellIntegrationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon'; +import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -80,6 +82,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { location: TerminalLocation, capabilities: ProcessCapability[], @IConfigurationService private readonly _configurationService: IConfigurationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @INotificationService private readonly _notificationService: INotificationService, @IStorageService private readonly _storageService: IStorageService, @@ -171,7 +174,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { return; } this._commandTrackerAddon.dispose(); - this._commandTrackerAddon = new CognisantCommandTrackerAddon(); + this._commandTrackerAddon = this._instantiationService.createInstance(CognisantCommandTrackerAddon); this._commandTrackerAddon.activate(this.raw); } From 29e30c7271a605818d0099da665e86eb2c05ec65 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 07:28:12 -0800 Subject: [PATCH 1131/2210] Prefer Date.now over Date.getTime for simplicity/perf --- .../browser/xterm/cognisantCommandTrackerAddon.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts index 2add71d31a9df..30861d3e3bb66 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -69,6 +69,8 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { case ShellIntegrationInteraction.CommandExecuted: this._currentCommand.commandExecutedY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY; + // TODO: Leverage key events on Windows between CommandStart and Executed to ensure we have the correct line + // TODO: Only do on this on Windows backends // Check if the command line is the same as the previous command line or if the // start Y differs from the executed Y. This is to catch the conpty case where the @@ -110,15 +112,13 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { if (this._currentCommand.command && !this._currentCommand.command.startsWith('\\') && this._currentCommand.command !== '') { this._commands.push({ command: this._currentCommand.command, - // TODO: Date.now() is equivalent? - timestamp: new Date().getTime(), + timestamp: Date.now(), cwd: this._cwd, exitCode: this._exitCode }); } - // TODO: Dispose - // this._currentCommand.previousCommandMarker?.dispose(); + this._currentCommand.previousCommandMarker?.dispose(); this._currentCommand.previousCommandMarker = this._currentCommand.marker; this._currentCommand.marker = undefined; break; From b0a27ab641dbf3775fc995872eea476100ba2d46 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 7 Jan 2022 16:30:51 +0100 Subject: [PATCH 1132/2210] Convert `IMouseTarget` to an OR-type --- .../editor/browser/controller/mouseHandler.ts | 30 +-- .../editor/browser/controller/mouseTarget.ts | 231 +++++++++--------- src/vs/editor/browser/editorBrowser.ts | 107 +++++++- .../browser/view/viewUserInputEvents.ts | 44 +--- .../contrib/colorPicker/colorContributions.ts | 14 +- src/vs/editor/contrib/folding/folding.ts | 5 +- src/vs/editor/contrib/hover/contentHover.ts | 9 +- src/vs/editor/contrib/hover/hover.ts | 20 +- .../inlayHints/inlayHintsController.ts | 8 +- .../contrib/inlayHints/inlayHintsHover.ts | 4 +- .../inlineCompletionsHoverParticipant.ts | 20 +- src/vs/monaco.d.ts | 109 ++++++++- .../comments/browser/commentThreadWidget.ts | 3 +- .../browser/breakpointEditorContribution.ts | 6 +- .../debug/browser/debugEditorContribution.ts | 10 +- .../browser/preferencesRenderers.ts | 2 +- .../preferences/browser/preferencesWidgets.ts | 4 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 3 +- .../testing/browser/testingDecorations.ts | 6 +- 19 files changed, 388 insertions(+), 247 deletions(-) diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index d08e64f03f542..e2e77261a06a7 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -8,8 +8,8 @@ import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent import { TimeoutTimer } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import { HitTestContext, IViewZoneData, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; -import { IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { HitTestContext, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; +import { IMouseTarget, IMouseTargetViewZoneData, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorMouseMoveMonitor, createEditorPagePosition, createCoordinatesRelativeToEditor } from 'vs/editor/browser/editorDom'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; @@ -260,7 +260,7 @@ export class MouseHandler extends ViewEventHandler { // Do not steal focus e.preventDefault(); } else if (targetIsViewZone) { - const viewZoneData = t.detail; + const viewZoneData = t.detail; if (this.viewHelper.shouldSuppressMouseDownOnViewZone(viewZoneData.viewZoneId)) { focus(); this._mouseDownOperation.start(t.type, e); @@ -455,7 +455,7 @@ class MouseDownOperation extends Disposable { this._currentSelection = e.selections[0]; } - private _getPositionOutsideEditor(e: EditorMouseEvent): MouseTarget | null { + private _getPositionOutsideEditor(e: EditorMouseEvent): IMouseTarget | null { const editorContent = e.editorPos; const model = this._context.model; const viewLayout = this._context.viewLayout; @@ -468,12 +468,12 @@ class MouseDownOperation extends Disposable { if (viewZoneData) { const newPosition = this._helpPositionJumpOverViewZone(viewZoneData); if (newPosition) { - return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, newPosition); + return MouseTarget.createOutsideEditor(mouseColumn, newPosition); } } const aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); - return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(aboveLineNumber, 1)); + return MouseTarget.createOutsideEditor(mouseColumn, new Position(aboveLineNumber, 1)); } if (e.posy > editorContent.y + editorContent.height) { @@ -482,28 +482,28 @@ class MouseDownOperation extends Disposable { if (viewZoneData) { const newPosition = this._helpPositionJumpOverViewZone(viewZoneData); if (newPosition) { - return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, newPosition); + return MouseTarget.createOutsideEditor(mouseColumn, newPosition); } } const belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); - return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber))); + return MouseTarget.createOutsideEditor(mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber))); } const possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + e.relativePos.y); if (e.posx < editorContent.x) { - return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(possibleLineNumber, 1)); + return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, 1)); } if (e.posx > editorContent.x + editorContent.width) { - return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber))); + return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber))); } return null; } - private _findMousePosition(e: EditorMouseEvent, testEventTarget: boolean): MouseTarget | null { + private _findMousePosition(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget | null { const positionOutsideEditor = this._getPositionOutsideEditor(e); if (positionOutsideEditor) { return positionOutsideEditor; @@ -516,16 +516,16 @@ class MouseDownOperation extends Disposable { } if (t.type === MouseTargetType.CONTENT_VIEW_ZONE || t.type === MouseTargetType.GUTTER_VIEW_ZONE) { - const newPosition = this._helpPositionJumpOverViewZone(t.detail); + const newPosition = this._helpPositionJumpOverViewZone(t.detail); if (newPosition) { - return new MouseTarget(t.element, t.type, t.mouseColumn, newPosition, null, t.detail); + return MouseTarget.createViewZone(t.type, t.element, t.mouseColumn, newPosition, t.detail); } } return t; } - private _helpPositionJumpOverViewZone(viewZoneData: IViewZoneData): Position | null { + private _helpPositionJumpOverViewZone(viewZoneData: IMouseTargetViewZoneData): Position | null { // Force position on view zones to go above or below depending on where selection started from const selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn); const positionBefore = viewZoneData.positionBefore; @@ -541,7 +541,7 @@ class MouseDownOperation extends Disposable { return null; } - private _dispatchMouse(position: MouseTarget, inSelectionMode: boolean): void { + private _dispatchMouse(position: IMouseTarget, inSelectionMode: boolean): void { if (!position.position) { return; } diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 5aadb8190aec7..cf279032cb00e 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; -import { IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { IMouseTargetContentEmptyData, IMouseTargetMarginData, IMouseTarget, IMouseTargetContentEmpty, IMouseTargetContentText, IMouseTargetContentWidget, IMouseTargetMargin, IMouseTargetOutsideEditor, IMouseTargetOverlayWidget, IMouseTargetScrollbar, IMouseTargetTextarea, IMouseTargetUnknown, IMouseTargetViewZone, IMouseTargetContentTextData, IMouseTargetViewZoneData, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { ClientCoordinates, EditorMouseEvent, EditorPagePosition, PageCoordinates, CoordinatesRelativeToEditor } from 'vs/editor/browser/editorDom'; import { PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart'; import { ViewLine } from 'vs/editor/browser/viewParts/lines/viewLine'; @@ -21,35 +21,9 @@ import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/ import { PositionAffinity } from 'vs/editor/common/model'; import { InjectedText } from 'vs/editor/common/viewModel/modelLineProjectionData'; -export interface IViewZoneData { - viewZoneId: string; - positionBefore: Position | null; - positionAfter: Position | null; - position: Position; - afterLineNumber: number; -} - -export interface IMarginData { - isAfterLines: boolean; - glyphMarginLeft: number; - glyphMarginWidth: number; - lineNumbersWidth: number; - offsetX: number; -} - -export interface IEmptyContentData { - isAfterLines: boolean; - horizontalDistanceToText?: number; -} - -export interface ITextContentData { - mightBeForeignElement: boolean; - injectedText: InjectedText | null; -} - const enum HitTestResultType { - Unknown = 0, - Content = 1, + Unknown, + Content, } class UnknownHitTestResult { @@ -87,25 +61,46 @@ export class PointerHandlerLastRenderData { ) { } } -export class MouseTarget implements IMouseTarget { +export class MouseTarget { - public readonly element: Element | null; - public readonly type: MouseTargetType; - public readonly mouseColumn: number; - public readonly position: Position | null; - public readonly range: EditorRange | null; - public readonly detail: any; - - constructor(element: Element | null, type: MouseTargetType, mouseColumn: number = 0, position: Position | null = null, range: EditorRange | null = null, detail: any = null) { - this.element = element; - this.type = type; - this.mouseColumn = mouseColumn; - this.position = position; + private static _deduceRage(position: Position): EditorRange; + private static _deduceRage(position: Position, range: EditorRange | null): EditorRange; + private static _deduceRage(position: Position | null): EditorRange | null; + private static _deduceRage(position: Position | null, range: EditorRange | null = null): EditorRange | null { if (!range && position) { - range = new EditorRange(position.lineNumber, position.column, position.lineNumber, position.column); + return new EditorRange(position.lineNumber, position.column, position.lineNumber, position.column); } - this.range = range; - this.detail = detail; + return range ?? null; + } + public static createUnknown(element: Element | null, mouseColumn: number, position: Position | null): IMouseTargetUnknown { + return { type: MouseTargetType.UNKNOWN, element, mouseColumn, position, range: this._deduceRage(position) }; + } + public static createTextarea(element: Element | null, mouseColumn: number): IMouseTargetTextarea { + return { type: MouseTargetType.TEXTAREA, element, mouseColumn, position: null, range: null }; + } + public static createMargin(type: MouseTargetType.GUTTER_GLYPH_MARGIN | MouseTargetType.GUTTER_LINE_NUMBERS | MouseTargetType.GUTTER_LINE_DECORATIONS, element: Element | null, mouseColumn: number, position: Position, range: EditorRange, detail: IMouseTargetMarginData): IMouseTargetMargin { + return { type, element, mouseColumn, position, range, detail }; + } + public static createViewZone(type: MouseTargetType.GUTTER_VIEW_ZONE | MouseTargetType.CONTENT_VIEW_ZONE, element: Element | null, mouseColumn: number, position: Position, detail: IMouseTargetViewZoneData): IMouseTargetViewZone { + return { type, element, mouseColumn, position, range: this._deduceRage(position), detail }; + } + public static createContentText(element: Element | null, mouseColumn: number, position: Position, range: EditorRange | null, detail: IMouseTargetContentTextData): IMouseTargetContentText { + return { type: MouseTargetType.CONTENT_TEXT, element, mouseColumn, position, range: this._deduceRage(position, range), detail }; + } + public static createContentEmpty(element: Element | null, mouseColumn: number, position: Position, detail: IMouseTargetContentEmptyData): IMouseTargetContentEmpty { + return { type: MouseTargetType.CONTENT_EMPTY, element, mouseColumn, position, range: this._deduceRage(position), detail }; + } + public static createContentWidget(element: Element | null, mouseColumn: number, detail: string): IMouseTargetContentWidget { + return { type: MouseTargetType.CONTENT_WIDGET, element, mouseColumn, position: null, range: null, detail }; + } + public static createScrollbar(element: Element | null, mouseColumn: number, position: Position): IMouseTargetScrollbar { + return { type: MouseTargetType.SCROLLBAR, element, mouseColumn, position, range: this._deduceRage(position) }; + } + public static createOverlayWidget(element: Element | null, mouseColumn: number, detail: string): IMouseTargetOverlayWidget { + return { type: MouseTargetType.OVERLAY_WIDGET, element, mouseColumn, position: null, range: null, detail }; + } + public static createOutsideEditor(mouseColumn: number, position: Position): IMouseTargetOutsideEditor { + return { type: MouseTargetType.OUTSIDE_EDITOR, element: null, mouseColumn, position, range: this._deduceRage(position) }; } private static _typeToString(type: MouseTargetType): string { @@ -149,11 +144,7 @@ export class MouseTarget implements IMouseTarget { } public static toString(target: IMouseTarget): string { - return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + target.detail; - } - - public toString(): string { - return MouseTarget.toString(this); + return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + JSON.stringify((target).detail); } } @@ -249,11 +240,11 @@ export class HitTestContext { this._viewHelper = viewHelper; } - public getZoneAtCoord(mouseVerticalOffset: number): IViewZoneData | null { + public getZoneAtCoord(mouseVerticalOffset: number): IMouseTargetViewZoneData | null { return HitTestContext.getZoneAtCoord(this._context, mouseVerticalOffset); } - public static getZoneAtCoord(context: ViewContext, mouseVerticalOffset: number): IViewZoneData | null { + public static getZoneAtCoord(context: ViewContext, mouseVerticalOffset: number): IMouseTargetViewZoneData | null { // The target is either a view zone or the empty space after the last view-line const viewZoneWhitespace = context.viewLayout.getWhitespaceAtVerticalOffset(mouseVerticalOffset); @@ -418,24 +409,40 @@ class HitTestRequest extends BareHitTestRequest { return `pos(${this.pos.x},${this.pos.y}), editorPos(${this.editorPos.x},${this.editorPos.y}), relativePos(${this.relativePos.x},${this.relativePos.y}), mouseVerticalOffset: ${this.mouseVerticalOffset}, mouseContentHorizontalOffset: ${this.mouseContentHorizontalOffset}\n\ttarget: ${this.target ? (this.target).outerHTML : null}`; } - public fulfill(type: MouseTargetType.UNKNOWN, position?: Position | null, range?: EditorRange | null): MouseTarget; - public fulfill(type: MouseTargetType.TEXTAREA, position: Position | null): MouseTarget; - public fulfill(type: MouseTargetType.GUTTER_GLYPH_MARGIN | MouseTargetType.GUTTER_LINE_NUMBERS | MouseTargetType.GUTTER_LINE_DECORATIONS, position: Position, range: EditorRange, detail: IMarginData): MouseTarget; - public fulfill(type: MouseTargetType.GUTTER_VIEW_ZONE | MouseTargetType.CONTENT_VIEW_ZONE, position: Position, range: null, detail: IViewZoneData): MouseTarget; - public fulfill(type: MouseTargetType.CONTENT_TEXT, position: Position | null, range: EditorRange | null, detail: ITextContentData): MouseTarget; - public fulfill(type: MouseTargetType.CONTENT_EMPTY, position: Position | null, range: EditorRange | null, detail: IEmptyContentData): MouseTarget; - public fulfill(type: MouseTargetType.CONTENT_WIDGET, position: null, range: null, detail: string): MouseTarget; - public fulfill(type: MouseTargetType.SCROLLBAR, position: Position): MouseTarget; - public fulfill(type: MouseTargetType.OVERLAY_WIDGET, position: null, range: null, detail: string): MouseTarget; - // public fulfill(type: MouseTargetType.OVERVIEW_RULER, position?: Position | null, range?: EditorRange | null, detail?: any): MouseTarget; - // public fulfill(type: MouseTargetType.OUTSIDE_EDITOR, position?: Position | null, range?: EditorRange | null, detail?: any): MouseTarget; - public fulfill(type: MouseTargetType, position: Position | null = null, range: EditorRange | null = null, detail: any = null): MouseTarget { - let mouseColumn = this.mouseColumn; + private _getMouseColumn(position: Position | null = null): number { if (position && position.column < this._ctx.model.getLineMaxColumn(position.lineNumber)) { // Most likely, the line contains foreign decorations... - mouseColumn = CursorColumns.visibleColumnFromColumn(this._ctx.model.getLineContent(position.lineNumber), position.column, this._ctx.model.getTextModelOptions().tabSize) + 1; + return CursorColumns.visibleColumnFromColumn(this._ctx.model.getLineContent(position.lineNumber), position.column, this._ctx.model.getTextModelOptions().tabSize) + 1; } - return new MouseTarget(this.target, type, mouseColumn, position, range, detail); + return this.mouseColumn; + } + + public fulfillUnknown(position: Position | null = null): IMouseTargetUnknown { + return MouseTarget.createUnknown(this.target, this._getMouseColumn(position), position); + } + public fulfillTextarea(): IMouseTargetTextarea { + return MouseTarget.createTextarea(this.target, this._getMouseColumn()); + } + public fulfillMargin(type: MouseTargetType.GUTTER_GLYPH_MARGIN | MouseTargetType.GUTTER_LINE_NUMBERS | MouseTargetType.GUTTER_LINE_DECORATIONS, position: Position, range: EditorRange, detail: IMouseTargetMarginData): IMouseTargetMargin { + return MouseTarget.createMargin(type, this.target, this._getMouseColumn(position), position, range, detail); + } + public fulfillViewZone(type: MouseTargetType.GUTTER_VIEW_ZONE | MouseTargetType.CONTENT_VIEW_ZONE, position: Position, detail: IMouseTargetViewZoneData): IMouseTargetViewZone { + return MouseTarget.createViewZone(type, this.target, this._getMouseColumn(position), position, detail); + } + public fulfillContentText(position: Position, range: EditorRange | null, detail: IMouseTargetContentTextData): IMouseTargetContentText { + return MouseTarget.createContentText(this.target, this._getMouseColumn(position), position, range, detail); + } + public fulfillContentEmpty(position: Position, detail: IMouseTargetContentEmptyData): IMouseTargetContentEmpty { + return MouseTarget.createContentEmpty(this.target, this._getMouseColumn(position), position, detail); + } + public fulfillContentWidget(detail: string): IMouseTargetContentWidget { + return MouseTarget.createContentWidget(this.target, this._getMouseColumn(), detail); + } + public fulfillScrollbar(position: Position): IMouseTargetScrollbar { + return MouseTarget.createScrollbar(this.target, this._getMouseColumn(position), position); + } + public fulfillOverlayWidget(detail: string): IMouseTargetOverlayWidget { + return MouseTarget.createOverlayWidget(this.target, this._getMouseColumn(), detail); } public withTarget(target: Element | null): HitTestRequest { @@ -447,9 +454,9 @@ interface ResolvedHitTestRequest extends HitTestRequest { readonly target: Element; } -const EMPTY_CONTENT_AFTER_LINES: IEmptyContentData = { isAfterLines: true }; +const EMPTY_CONTENT_AFTER_LINES: IMouseTargetContentEmptyData = { isAfterLines: true }; -function createEmptyContentDataInLines(horizontalDistanceToText: number): IEmptyContentData { +function createEmptyContentDataInLines(horizontalDistanceToText: number): IMouseTargetContentEmptyData { return { isAfterLines: false, horizontalDistanceToText: horizontalDistanceToText @@ -488,15 +495,15 @@ export class MouseTargetFactory { const request = new HitTestRequest(ctx, editorPos, pos, relativePos, target); try { const r = MouseTargetFactory._createMouseTarget(ctx, request, false); - // console.log(r.toString()); + // console.log(MouseTarget.toString(r)); return r; } catch (err) { // console.log(err); - return request.fulfill(MouseTargetType.UNKNOWN); + return request.fulfillUnknown(); } } - private static _createMouseTarget(ctx: HitTestContext, request: HitTestRequest, domHitTestExecuted: boolean): MouseTarget { + private static _createMouseTarget(ctx: HitTestContext, request: HitTestRequest, domHitTestExecuted: boolean): IMouseTarget { // console.log(`${domHitTestExecuted ? '=>' : ''}CAME IN REQUEST: ${request}`); @@ -504,7 +511,7 @@ export class MouseTargetFactory { if (request.target === null) { if (domHitTestExecuted) { // Still no target... and we have already executed hit test... - return request.fulfill(MouseTargetType.UNKNOWN); + return request.fulfillUnknown(); } const hitTestResult = MouseTargetFactory._doHitTest(ctx, request); @@ -519,7 +526,7 @@ export class MouseTargetFactory { // we know for a fact that request.target is not null const resolvedRequest = request; - let result: MouseTarget | null = null; + let result: IMouseTarget | null = null; result = result || MouseTargetFactory._hitTestContentWidget(ctx, resolvedRequest); result = result || MouseTargetFactory._hitTestOverlayWidget(ctx, resolvedRequest); @@ -532,36 +539,36 @@ export class MouseTargetFactory { result = result || MouseTargetFactory._hitTestViewLines(ctx, resolvedRequest, domHitTestExecuted); result = result || MouseTargetFactory._hitTestScrollbar(ctx, resolvedRequest); - return (result || request.fulfill(MouseTargetType.UNKNOWN)); + return (result || request.fulfillUnknown()); } - private static _hitTestContentWidget(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestContentWidget(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { // Is it a content widget? if (ElementPath.isChildOfContentWidgets(request.targetPath) || ElementPath.isChildOfOverflowingContentWidgets(request.targetPath)) { const widgetId = ctx.findAttribute(request.target, 'widgetId'); if (widgetId) { - return request.fulfill(MouseTargetType.CONTENT_WIDGET, null, null, widgetId); + return request.fulfillContentWidget(widgetId); } else { - return request.fulfill(MouseTargetType.UNKNOWN); + return request.fulfillUnknown(); } } return null; } - private static _hitTestOverlayWidget(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestOverlayWidget(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { // Is it an overlay widget? if (ElementPath.isChildOfOverlayWidgets(request.targetPath)) { const widgetId = ctx.findAttribute(request.target, 'widgetId'); if (widgetId) { - return request.fulfill(MouseTargetType.OVERLAY_WIDGET, null, null, widgetId); + return request.fulfillOverlayWidget(widgetId); } else { - return request.fulfill(MouseTargetType.UNKNOWN); + return request.fulfillUnknown(); } } return null; } - private static _hitTestViewCursor(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestViewCursor(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { if (request.target) { // Check if we've hit a painted cursor @@ -570,7 +577,7 @@ export class MouseTargetFactory { for (const d of lastViewCursorsRenderData) { if (request.target === d.domNode) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position, null, { mightBeForeignElement: false, injectedText: null }); + return request.fulfillContentText(d.position, null, { mightBeForeignElement: false, injectedText: null }); } } } @@ -602,7 +609,7 @@ export class MouseTargetFactory { cursorVerticalOffset <= mouseVerticalOffset && mouseVerticalOffset <= cursorVerticalOffset + d.height ) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position, null, { mightBeForeignElement: false, injectedText: null }); + return request.fulfillContentText(d.position, null, { mightBeForeignElement: false, injectedText: null }); } } } @@ -610,33 +617,33 @@ export class MouseTargetFactory { return null; } - private static _hitTestViewZone(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestViewZone(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { const viewZoneData = ctx.getZoneAtCoord(request.mouseVerticalOffset); if (viewZoneData) { const mouseTargetType = (request.isInContentArea ? MouseTargetType.CONTENT_VIEW_ZONE : MouseTargetType.GUTTER_VIEW_ZONE); - return request.fulfill(mouseTargetType, viewZoneData.position, null, viewZoneData); + return request.fulfillViewZone(mouseTargetType, viewZoneData.position, viewZoneData); } return null; } - private static _hitTestTextArea(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestTextArea(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { // Is it the textarea? if (ElementPath.isTextArea(request.targetPath)) { if (ctx.lastRenderData.lastTextareaPosition) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, ctx.lastRenderData.lastTextareaPosition, null, { mightBeForeignElement: false, injectedText: null }); + return request.fulfillContentText(ctx.lastRenderData.lastTextareaPosition, null, { mightBeForeignElement: false, injectedText: null }); } - return request.fulfill(MouseTargetType.TEXTAREA, ctx.lastRenderData.lastTextareaPosition); + return request.fulfillTextarea(); } return null; } - private static _hitTestMargin(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestMargin(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { if (request.isInMarginArea) { const res = ctx.getFullLineRangeAtCoord(request.mouseVerticalOffset); const pos = res.range.getStartPosition(); let offset = Math.abs(request.relativePos.x); - const detail: IMarginData = { + const detail: IMouseTargetMarginData = { isAfterLines: res.isAfterLines, glyphMarginLeft: ctx.layoutInfo.glyphMarginLeft, glyphMarginWidth: ctx.layoutInfo.glyphMarginWidth, @@ -648,29 +655,29 @@ export class MouseTargetFactory { if (offset <= ctx.layoutInfo.glyphMarginWidth) { // On the glyph margin - return request.fulfill(MouseTargetType.GUTTER_GLYPH_MARGIN, pos, res.range, detail); + return request.fulfillMargin(MouseTargetType.GUTTER_GLYPH_MARGIN, pos, res.range, detail); } offset -= ctx.layoutInfo.glyphMarginWidth; if (offset <= ctx.layoutInfo.lineNumbersWidth) { // On the line numbers - return request.fulfill(MouseTargetType.GUTTER_LINE_NUMBERS, pos, res.range, detail); + return request.fulfillMargin(MouseTargetType.GUTTER_LINE_NUMBERS, pos, res.range, detail); } offset -= ctx.layoutInfo.lineNumbersWidth; // On the line decorations - return request.fulfill(MouseTargetType.GUTTER_LINE_DECORATIONS, pos, res.range, detail); + return request.fulfillMargin(MouseTargetType.GUTTER_LINE_DECORATIONS, pos, res.range, detail); } return null; } - private static _hitTestViewLines(ctx: HitTestContext, request: ResolvedHitTestRequest, domHitTestExecuted: boolean): MouseTarget | null { + private static _hitTestViewLines(ctx: HitTestContext, request: ResolvedHitTestRequest, domHitTestExecuted: boolean): IMouseTarget | null { if (!ElementPath.isChildOfViewLines(request.targetPath)) { return null; } if (ctx.isInTopPadding(request.mouseVerticalOffset)) { - return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(1, 1), null, EMPTY_CONTENT_AFTER_LINES); + return request.fulfillContentEmpty(new Position(1, 1), EMPTY_CONTENT_AFTER_LINES); } // Check if it is below any lines and any view zones @@ -678,7 +685,7 @@ export class MouseTargetFactory { // This most likely indicates it happened after the last view-line const lineCount = ctx.model.getLineCount(); const maxLineColumn = ctx.model.getLineMaxColumn(lineCount); - return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineCount, maxLineColumn), null, EMPTY_CONTENT_AFTER_LINES); + return request.fulfillContentEmpty(new Position(lineCount, maxLineColumn), EMPTY_CONTENT_AFTER_LINES); } if (domHitTestExecuted) { @@ -689,19 +696,19 @@ export class MouseTargetFactory { if (ctx.model.getLineLength(lineNumber) === 0) { const lineWidth = ctx.getLineWidth(lineNumber); const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); - return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, 1), null, detail); + return request.fulfillContentEmpty(new Position(lineNumber, 1), detail); } const lineWidth = ctx.getLineWidth(lineNumber); if (request.mouseContentHorizontalOffset >= lineWidth) { const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); const pos = new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber)); - return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, null, detail); + return request.fulfillContentEmpty(pos, detail); } } // We have already executed hit test... - return request.fulfill(MouseTargetType.UNKNOWN); + return request.fulfillUnknown(); } const hitTestResult = MouseTargetFactory._doHitTest(ctx, request); @@ -713,36 +720,36 @@ export class MouseTargetFactory { return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true); } - private static _hitTestMinimap(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestMinimap(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { if (ElementPath.isChildOfMinimap(request.targetPath)) { const possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset); const maxColumn = ctx.model.getLineMaxColumn(possibleLineNumber); - return request.fulfill(MouseTargetType.SCROLLBAR, new Position(possibleLineNumber, maxColumn)); + return request.fulfillScrollbar(new Position(possibleLineNumber, maxColumn)); } return null; } - private static _hitTestScrollbarSlider(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestScrollbarSlider(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { if (ElementPath.isChildOfScrollableElement(request.targetPath)) { if (request.target && request.target.nodeType === 1) { const className = request.target.className; if (className && /\b(slider|scrollbar)\b/.test(className)) { const possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset); const maxColumn = ctx.model.getLineMaxColumn(possibleLineNumber); - return request.fulfill(MouseTargetType.SCROLLBAR, new Position(possibleLineNumber, maxColumn)); + return request.fulfillScrollbar(new Position(possibleLineNumber, maxColumn)); } } } return null; } - private static _hitTestScrollbar(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { + private static _hitTestScrollbar(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { // Is it the overview ruler? // Is it a child of the scrollable element? if (ElementPath.isChildOfScrollableElement(request.targetPath)) { const possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset); const maxColumn = ctx.model.getLineMaxColumn(possibleLineNumber); - return request.fulfill(MouseTargetType.SCROLLBAR, new Position(possibleLineNumber, maxColumn)); + return request.fulfillScrollbar(new Position(possibleLineNumber, maxColumn)); } return null; @@ -763,7 +770,7 @@ export class MouseTargetFactory { return (chars + 1); } - private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position, injectedText: InjectedText | null): MouseTarget { + private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position, injectedText: InjectedText | null): IMouseTarget { const lineNumber = pos.lineNumber; const column = pos.column; @@ -771,19 +778,19 @@ export class MouseTargetFactory { if (request.mouseContentHorizontalOffset > lineWidth) { const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); - return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, null, detail); + return request.fulfillContentEmpty(pos, detail); } const visibleRange = ctx.visibleRangeForPosition(lineNumber, column); if (!visibleRange) { - return request.fulfill(MouseTargetType.UNKNOWN, pos); + return request.fulfillUnknown(pos); } const columnHorizontalOffset = visibleRange.left; if (request.mouseContentHorizontalOffset === columnHorizontalOffset) { - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !!injectedText, injectedText }); + return request.fulfillContentText(pos, null, { mightBeForeignElement: !!injectedText, injectedText }); } // Let's define a, b, c and check if the offset is in between them... @@ -816,10 +823,10 @@ export class MouseTargetFactory { const curr = points[i]; if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) { const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column); - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); + return request.fulfillContentText(pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); } } - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); + return request.fulfillContentText(pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); } /** diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 8733ada2f7126..f606fefe00b53 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -19,6 +19,7 @@ import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IDiffComputationResult } from 'vs/editor/common/services/editorWorker'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; +import { InjectedText } from 'vs/editor/common/viewModel/modelLineProjectionData'; /** * A view zone is a full horizontal rectangle that 'pushes' text down. @@ -280,19 +281,11 @@ export const enum MouseTargetType { */ OUTSIDE_EDITOR, } - -/** - * Target hit with the mouse in the editor. - */ -export interface IMouseTarget { +export interface IBaseMouseTarget { /** * The target element */ readonly element: Element | null; - /** - * The target type - */ - readonly type: MouseTargetType; /** * The 'approximate' editor position */ @@ -305,11 +298,103 @@ export interface IMouseTarget { * The 'approximate' editor range */ readonly range: Range | null; +} +export interface IMouseTargetUnknown extends IBaseMouseTarget { + readonly type: MouseTargetType.UNKNOWN; +} +export interface IMouseTargetTextarea extends IBaseMouseTarget { + readonly type: MouseTargetType.TEXTAREA; + readonly position: null; + readonly range: null; +} +export interface IMouseTargetMarginData { + readonly isAfterLines: boolean; + readonly glyphMarginLeft: number; + readonly glyphMarginWidth: number; + readonly lineNumbersWidth: number; + readonly offsetX: number; +} +export interface IMouseTargetMargin extends IBaseMouseTarget { + readonly type: MouseTargetType.GUTTER_GLYPH_MARGIN | MouseTargetType.GUTTER_LINE_NUMBERS | MouseTargetType.GUTTER_LINE_DECORATIONS; + readonly position: Position; + readonly range: Range; + readonly detail: IMouseTargetMarginData; +} +export interface IMouseTargetViewZoneData { + readonly viewZoneId: string; + readonly positionBefore: Position | null; + readonly positionAfter: Position | null; + readonly position: Position; + readonly afterLineNumber: number; +} +export interface IMouseTargetViewZone extends IBaseMouseTarget { + readonly type: MouseTargetType.GUTTER_VIEW_ZONE | MouseTargetType.CONTENT_VIEW_ZONE; + readonly position: Position; + readonly range: Range; + readonly detail: IMouseTargetViewZoneData; +} +export interface IMouseTargetContentTextData { + readonly mightBeForeignElement: boolean; /** - * Some extra detail. + * @internal */ - readonly detail: any; + readonly injectedText: InjectedText | null; +} +export interface IMouseTargetContentText extends IBaseMouseTarget { + readonly type: MouseTargetType.CONTENT_TEXT; + readonly position: Position; + readonly range: Range; + readonly detail: IMouseTargetContentTextData; +} +export interface IMouseTargetContentEmptyData { + readonly isAfterLines: boolean; + readonly horizontalDistanceToText?: number; +} +export interface IMouseTargetContentEmpty extends IBaseMouseTarget { + readonly type: MouseTargetType.CONTENT_EMPTY; + readonly position: Position; + readonly range: Range; + readonly detail: IMouseTargetContentEmptyData; +} +export interface IMouseTargetContentWidget extends IBaseMouseTarget { + readonly type: MouseTargetType.CONTENT_WIDGET; + readonly position: null; + readonly range: null; + readonly detail: string; +} +export interface IMouseTargetOverlayWidget extends IBaseMouseTarget { + readonly type: MouseTargetType.OVERLAY_WIDGET; + readonly position: null; + readonly range: null; + readonly detail: string; } +export interface IMouseTargetScrollbar extends IBaseMouseTarget { + readonly type: MouseTargetType.SCROLLBAR; + readonly position: Position; + readonly range: Range; +} +export interface IMouseTargetOverviewRuler extends IBaseMouseTarget { + readonly type: MouseTargetType.OVERVIEW_RULER; +} +export interface IMouseTargetOutsideEditor extends IBaseMouseTarget { + readonly type: MouseTargetType.OUTSIDE_EDITOR; +} +/** + * Target hit with the mouse in the editor. + */ +export type IMouseTarget = ( + IMouseTargetUnknown + | IMouseTargetTextarea + | IMouseTargetMargin + | IMouseTargetViewZone + | IMouseTargetContentText + | IMouseTargetContentEmpty + | IMouseTargetContentWidget + | IMouseTargetOverlayWidget + | IMouseTargetScrollbar + | IMouseTargetOverviewRuler + | IMouseTargetOutsideEditor +); /** * A mouse event originating from the editor. */ diff --git a/src/vs/editor/browser/view/viewUserInputEvents.ts b/src/vs/editor/browser/view/viewUserInputEvents.ts index 2a58f89d578ef..03d7e4ac35ca1 100644 --- a/src/vs/editor/browser/view/viewUserInputEvents.ts +++ b/src/vs/editor/browser/view/viewUserInputEvents.ts @@ -4,10 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { MouseTarget } from 'vs/editor/browser/controller/mouseTarget'; -import { IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; +import { IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -118,36 +115,13 @@ export class ViewUserInputEvents { } public static convertViewToModelMouseTarget(target: IMouseTarget, coordinatesConverter: ICoordinatesConverter): IMouseTarget { - return new ExternalMouseTarget( - target.element, - target.type, - target.mouseColumn, - target.position ? coordinatesConverter.convertViewPositionToModelPosition(target.position) : null, - target.range ? coordinatesConverter.convertViewRangeToModelRange(target.range) : null, - target.detail - ); - } -} - -class ExternalMouseTarget implements IMouseTarget { - - public readonly element: Element | null; - public readonly type: MouseTargetType; - public readonly mouseColumn: number; - public readonly position: Position | null; - public readonly range: Range | null; - public readonly detail: any; - - constructor(element: Element | null, type: MouseTargetType, mouseColumn: number, position: Position | null, range: Range | null, detail: any) { - this.element = element; - this.type = type; - this.mouseColumn = mouseColumn; - this.position = position; - this.range = range; - this.detail = detail; - } - - public toString(): string { - return MouseTarget.toString(this); + const result = { ...target }; + if (result.position) { + result.position = coordinatesConverter.convertViewPositionToModelPosition(result.position); + } + if (result.range) { + result.range = coordinatesConverter.convertViewRangeToModelRange(result.range); + } + return result; } } diff --git a/src/vs/editor/contrib/colorPicker/colorContributions.ts b/src/vs/editor/contrib/colorPicker/colorContributions.ts index 0867134562477..7d1f5b8d2e9f3 100644 --- a/src/vs/editor/contrib/colorPicker/colorContributions.ts +++ b/src/vs/editor/contrib/colorPicker/colorContributions.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { ITextContentData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; @@ -30,22 +29,21 @@ export class ColorContribution extends Disposable implements IEditorContribution } private onMouseDown(mouseEvent: IEditorMouseEvent) { - const targetType = mouseEvent.target.type; + const target = mouseEvent.target; - if (targetType !== MouseTargetType.CONTENT_TEXT) { + if (target.type !== MouseTargetType.CONTENT_TEXT) { return; } - const detail = (mouseEvent.target.detail); - if (!detail || !detail.injectedText) { + if (!target.detail.injectedText) { return; } - if (detail.injectedText.options.attachedData !== ColorDecorationInjectedTextMarker) { + if (target.detail.injectedText.options.attachedData !== ColorDecorationInjectedTextMarker) { return; } - if (!mouseEvent.target.range) { + if (!target.range) { return; } @@ -54,7 +52,7 @@ export class ColorContribution extends Disposable implements IEditorContribution return; } if (!hoverController.isColorPickerVisible()) { - const range = new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1); + const range = new Range(target.range.startLineNumber, target.range.startColumn + 1, target.range.endLineNumber, target.range.endColumn + 1); hoverController.showContentHover(range, HoverStartMode.Immediate, false); } } diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 5f4a6e28cf1f8..00cb71fef2ba0 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -11,7 +11,6 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import * as types from 'vs/base/common/types'; import 'vs/css!./folding'; -import { IEmptyContentData, IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction, registerEditorContribution, registerInstantiatedEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -397,7 +396,7 @@ export class FoldingController extends Disposable implements IEditorContribution let iconClicked = false; switch (e.target.type) { case MouseTargetType.GUTTER_LINE_DECORATIONS: { - const data = e.target.detail as IMarginData; + const data = e.target.detail; const offsetLeftInGutter = (e.target.element as HTMLElement).offsetLeft; const gutterOffsetX = data.offsetX - offsetLeftInGutter; @@ -413,7 +412,7 @@ export class FoldingController extends Disposable implements IEditorContribution } case MouseTargetType.CONTENT_EMPTY: { if (this._unfoldOnClickAfterEndOfLine && this.hiddenRangeModel.hasRanges()) { - const data = e.target.detail as IEmptyContentData; + const data = e.target.detail; if (!data.isAfterLines) { break; } diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index 698c5fd1d986d..3c255f0de423f 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -10,7 +10,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Constants } from 'vs/base/common/uint'; -import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; @@ -94,14 +93,14 @@ export class ContentHoverController extends Disposable { } private _shouldShowAt(mouseEvent: IEditorMouseEvent): boolean { - const targetType = mouseEvent.target.type; - if (targetType === MouseTargetType.CONTENT_TEXT) { + const target = mouseEvent.target; + if (target.type === MouseTargetType.CONTENT_TEXT) { return true; } - if (targetType === MouseTargetType.CONTENT_EMPTY) { + if (target.type === MouseTargetType.CONTENT_EMPTY) { const epsilon = this._editor.getOption(EditorOption.fontInfo).typicalHalfwidthCharacterWidth / 2; - const data = mouseEvent.target.detail; + const data = target.detail; if (data && !data.isAfterLines && typeof data.horizontalDistanceToText === 'number' && data.horizontalDistanceToText < epsilon) { // Let hover kick in even when the mouse is technically in the empty area after a line, given the distance is small enough return true; diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 56c389e947cda..425626de80585 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -100,20 +100,20 @@ export class ModesHoverController implements IEditorContribution { private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { this._isMouseDown = true; - const targetType = mouseEvent.target.type; + const target = mouseEvent.target; - if (targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ContentHoverWidget.ID) { + if (target.type === MouseTargetType.CONTENT_WIDGET && target.detail === ContentHoverWidget.ID) { this._hoverClicked = true; // mouse down on top of content hover widget return; } - if (targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === MarginHoverWidget.ID) { + if (target.type === MouseTargetType.OVERLAY_WIDGET && target.detail === MarginHoverWidget.ID) { // mouse down on top of overlay hover widget return; } - if (targetType !== MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail !== MarginHoverWidget.ID) { + if (target.type !== MouseTargetType.OVERLAY_WIDGET) { this._hoverClicked = false; } @@ -125,13 +125,13 @@ export class ModesHoverController implements IEditorContribution { } private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { - let targetType = mouseEvent.target.type; + const target = mouseEvent.target; if (this._isMouseDown && this._hoverClicked) { return; } - if (this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ContentHoverWidget.ID) { + if (this._isHoverSticky && target.type === MouseTargetType.CONTENT_WIDGET && target.detail === ContentHoverWidget.ID) { // mouse moved on top of content hover widget return; } @@ -142,14 +142,14 @@ export class ModesHoverController implements IEditorContribution { } if ( - !this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ContentHoverWidget.ID + !this._isHoverSticky && target.type === MouseTargetType.CONTENT_WIDGET && target.detail === ContentHoverWidget.ID && this._contentWidget?.isColorPickerVisible() ) { // though the hover is not sticky, the color picker needs to. return; } - if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === MarginHoverWidget.ID) { + if (this._isHoverSticky && target.type === MouseTargetType.OVERLAY_WIDGET && target.detail === MarginHoverWidget.ID) { // mouse moved on top of overlay hover widget return; } @@ -165,12 +165,12 @@ export class ModesHoverController implements IEditorContribution { return; } - if (targetType === MouseTargetType.GUTTER_GLYPH_MARGIN && mouseEvent.target.position) { + if (target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && target.position) { this._contentWidget?.hide(); if (!this._glyphWidget) { this._glyphWidget = new MarginHoverWidget(this._editor, this._languageService, this._openerService); } - this._glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber); + this._glyphWidget.startShowingAt(target.position.lineNumber); return; } diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index b712330af11dd..2bcd9945105b8 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -150,12 +150,12 @@ export class InlayHintsController implements IEditorContribution { gesture.onMouseMoveOrRelevantKeyDown(e => { const [mouseEvent] = e; - if (mouseEvent.target.type !== MouseTargetType.CONTENT_TEXT || typeof mouseEvent.target.detail !== 'object' || !mouseEvent.hasTriggerModifier) { + if (mouseEvent.target.type !== MouseTargetType.CONTENT_TEXT || !mouseEvent.hasTriggerModifier) { removeHighlight(); return; } const model = this._editor.getModel()!; - const options = mouseEvent.target.detail?.injectedText?.options; + const options = mouseEvent.target.detail.injectedText?.options; if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { this._activeInlayHintPart = options.attachedData; @@ -176,10 +176,10 @@ export class InlayHintsController implements IEditorContribution { }); gesture.onCancel(removeHighlight); gesture.onExecute(e => { - if (e.target.type !== MouseTargetType.CONTENT_TEXT || typeof e.target.detail !== 'object' || !e.hasTriggerModifier) { + if (e.target.type !== MouseTargetType.CONTENT_TEXT || !e.hasTriggerModifier) { return; } - const options = e.target.detail?.injectedText?.options; + const options = e.target.detail.injectedText?.options; if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { this._openerService.open(options.attachedData.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); } diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts index cc3ff5c39452f..583c3773a941c 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts @@ -29,10 +29,10 @@ export class InlayHintsHover extends MarkdownHoverParticipant { if (!controller) { return null; } - if (mouseEvent.target.type !== MouseTargetType.CONTENT_TEXT || typeof mouseEvent.target.detail !== 'object') { + if (mouseEvent.target.type !== MouseTargetType.CONTENT_TEXT) { return null; } - const options = mouseEvent.target.detail?.injectedText?.options; + const options = mouseEvent.target.detail.injectedText?.options; if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart)) { return null; } diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts index 2191515b9aa5c..ff6652b8f12ef 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts @@ -6,7 +6,6 @@ import * as dom from 'vs/base/browser/dom'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { ITextContentData, IViewZoneData } from 'vs/editor/browser/controller/mouseTarget'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; @@ -57,24 +56,25 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan if (!controller) { return null; } - if (mouseEvent.target.type === MouseTargetType.CONTENT_VIEW_ZONE) { + const target = mouseEvent.target; + if (target.type === MouseTargetType.CONTENT_VIEW_ZONE) { // handle the case where the mouse is over the view zone - const viewZoneData = mouseEvent.target.detail; + const viewZoneData = target.detail; if (controller.shouldShowHoverAtViewZone(viewZoneData.viewZoneId)) { return new HoverForeignElementAnchor(1000, this, Range.fromPositions(viewZoneData.positionBefore || viewZoneData.position, viewZoneData.positionBefore || viewZoneData.position)); } } - if (mouseEvent.target.type === MouseTargetType.CONTENT_EMPTY && mouseEvent.target.range) { + if (target.type === MouseTargetType.CONTENT_EMPTY) { // handle the case where the mouse is over the empty portion of a line following ghost text - if (controller.shouldShowHoverAt(mouseEvent.target.range)) { - return new HoverForeignElementAnchor(1000, this, mouseEvent.target.range); + if (controller.shouldShowHoverAt(target.range)) { + return new HoverForeignElementAnchor(1000, this, target.range); } } - if (mouseEvent.target.type === MouseTargetType.CONTENT_TEXT && mouseEvent.target.range && mouseEvent.target.detail) { + if (target.type === MouseTargetType.CONTENT_TEXT) { // handle the case where the mouse is directly over ghost text - const mightBeForeignElement = (mouseEvent.target.detail).mightBeForeignElement; - if (mightBeForeignElement && controller.shouldShowHoverAt(mouseEvent.target.range)) { - return new HoverForeignElementAnchor(1000, this, mouseEvent.target.range); + const mightBeForeignElement = target.detail.mightBeForeignElement; + if (mightBeForeignElement && controller.shouldShowHoverAt(target.range)) { + return new HoverForeignElementAnchor(1000, this, target.range); } } return null; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8b2707af5f257..b53a6c8b6450d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4682,18 +4682,11 @@ declare namespace monaco.editor { OUTSIDE_EDITOR = 13 } - /** - * Target hit with the mouse in the editor. - */ - export interface IMouseTarget { + export interface IBaseMouseTarget { /** * The target element */ readonly element: Element | null; - /** - * The target type - */ - readonly type: MouseTargetType; /** * The 'approximate' editor position */ @@ -4706,12 +4699,104 @@ declare namespace monaco.editor { * The 'approximate' editor range */ readonly range: Range | null; - /** - * Some extra detail. - */ - readonly detail: any; } + export interface IMouseTargetUnknown extends IBaseMouseTarget { + readonly type: MouseTargetType.UNKNOWN; + } + + export interface IMouseTargetTextarea extends IBaseMouseTarget { + readonly type: MouseTargetType.TEXTAREA; + readonly position: null; + readonly range: null; + } + + export interface IMouseTargetMarginData { + readonly isAfterLines: boolean; + readonly glyphMarginLeft: number; + readonly glyphMarginWidth: number; + readonly lineNumbersWidth: number; + readonly offsetX: number; + } + + export interface IMouseTargetMargin extends IBaseMouseTarget { + readonly type: MouseTargetType.GUTTER_GLYPH_MARGIN | MouseTargetType.GUTTER_LINE_NUMBERS | MouseTargetType.GUTTER_LINE_DECORATIONS; + readonly position: Position; + readonly range: Range; + readonly detail: IMouseTargetMarginData; + } + + export interface IMouseTargetViewZoneData { + readonly viewZoneId: string; + readonly positionBefore: Position | null; + readonly positionAfter: Position | null; + readonly position: Position; + readonly afterLineNumber: number; + } + + export interface IMouseTargetViewZone extends IBaseMouseTarget { + readonly type: MouseTargetType.GUTTER_VIEW_ZONE | MouseTargetType.CONTENT_VIEW_ZONE; + readonly position: Position; + readonly range: Range; + readonly detail: IMouseTargetViewZoneData; + } + + export interface IMouseTargetContentTextData { + readonly mightBeForeignElement: boolean; + } + + export interface IMouseTargetContentText extends IBaseMouseTarget { + readonly type: MouseTargetType.CONTENT_TEXT; + readonly position: Position; + readonly range: Range; + readonly detail: IMouseTargetContentTextData; + } + + export interface IMouseTargetContentEmptyData { + readonly isAfterLines: boolean; + readonly horizontalDistanceToText?: number; + } + + export interface IMouseTargetContentEmpty extends IBaseMouseTarget { + readonly type: MouseTargetType.CONTENT_EMPTY; + readonly position: Position; + readonly range: Range; + readonly detail: IMouseTargetContentEmptyData; + } + + export interface IMouseTargetContentWidget extends IBaseMouseTarget { + readonly type: MouseTargetType.CONTENT_WIDGET; + readonly position: null; + readonly range: null; + readonly detail: string; + } + + export interface IMouseTargetOverlayWidget extends IBaseMouseTarget { + readonly type: MouseTargetType.OVERLAY_WIDGET; + readonly position: null; + readonly range: null; + readonly detail: string; + } + + export interface IMouseTargetScrollbar extends IBaseMouseTarget { + readonly type: MouseTargetType.SCROLLBAR; + readonly position: Position; + readonly range: Range; + } + + export interface IMouseTargetOverviewRuler extends IBaseMouseTarget { + readonly type: MouseTargetType.OVERVIEW_RULER; + } + + export interface IMouseTargetOutsideEditor extends IBaseMouseTarget { + readonly type: MouseTargetType.OUTSIDE_EDITOR; + } + + /** + * Target hit with the mouse in the editor. + */ + export type IMouseTarget = (IMouseTargetUnknown | IMouseTargetTextarea | IMouseTargetMargin | IMouseTargetViewZone | IMouseTargetContentText | IMouseTargetContentEmpty | IMouseTargetContentWidget | IMouseTargetOverlayWidget | IMouseTargetScrollbar | IMouseTargetOverviewRuler | IMouseTargetOutsideEditor); + /** * A mouse event originating from the editor. */ diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index db1bc3f0159d3..9247d145ca019 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -13,7 +13,6 @@ import * as strings from 'vs/base/common/strings'; import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -73,7 +72,7 @@ export function parseMouseDownInfoFromEvent(e: IEditorMouseEvent) { return null; } - const data = e.target.detail as IMarginData; + const data = e.target.detail; const gutterOffsetX = data.offsetX - data.glyphMarginWidth - data.lineNumbersWidth - data.glyphMarginLeft; // don't collide with folding and git decorations diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index dc7123b4af8bb..5042a44eede24 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -16,7 +16,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; -import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -209,9 +208,8 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi return; } - const data = e.target.detail as IMarginData; const model = this.editor.getModel(); - if (!e.target.position || !model || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { + if (!e.target.position || !model || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.detail.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { return; } const canSetBreakpoints = this.debugService.canSetBreakpointsIn(model); @@ -296,7 +294,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi const model = this.editor.getModel(); if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.canSetBreakpointsIn(model) && this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { - const data = e.target.detail as IMarginData; + const data = e.target.detail; if (!data.isAfterLines) { showBreakpointHintAtLineNumber = e.target.position.lineNumber; } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 2b42e4bebb54d..555dd51545745 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -409,16 +409,16 @@ export class DebugEditorContribution implements IDebugEditorContribution { return; } - const targetType = mouseEvent.target.type; + const target = mouseEvent.target; const stopKey = env.isMacintosh ? 'metaKey' : 'ctrlKey'; - if (targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === DebugHoverWidget.ID && !(mouseEvent.event)[stopKey]) { + if (target.type === MouseTargetType.CONTENT_WIDGET && target.detail === DebugHoverWidget.ID && !(mouseEvent.event)[stopKey]) { // mouse moved on top of debug hover widget return; } - if (targetType === MouseTargetType.CONTENT_TEXT) { - if (mouseEvent.target.range && !mouseEvent.target.range.equalsRange(this.hoverRange)) { - this.hoverRange = mouseEvent.target.range; + if (target.type === MouseTargetType.CONTENT_TEXT) { + if (target.range && !target.range.equalsRange(this.hoverRange)) { + this.hoverRange = target.range; this.hideHoverScheduler.cancel(); this.showHoverScheduler.schedule(); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 823d2b66b0b86..eea6954dc1cb7 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -236,7 +236,7 @@ class EditSettingRenderer extends Disposable { private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget | undefined { if (mouseMoveEvent.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN) { - const line = mouseMoveEvent.target.position!.lineNumber; + const line = mouseMoveEvent.target.position.lineNumber; if (this.editPreferenceWidgetForMouseMove.getLine() === line && this.editPreferenceWidgetForMouseMove.isVisible()) { return this.editPreferenceWidgetForMouseMove; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index e2b01420490c8..631687026525d 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -17,7 +17,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; @@ -506,8 +505,7 @@ export class EditPreferenceWidget extends Disposable { super(); this._editPreferenceDecoration = []; this._register(this.editor.onMouseDown((e: IEditorMouseEvent) => { - const data = e.target.detail as IMarginData; - if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.isVisible()) { + if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.detail.isAfterLines || !this.isVisible()) { return; } this._onClick.fire(e); diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 4d55580fe1cc1..642ea42611a86 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -42,7 +42,6 @@ import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/men import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition } from 'vs/editor/common/model'; import { sortedDiff } from 'vs/base/common/arrays'; -import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ISplice } from 'vs/base/common/sequence'; import { createStyleSheet } from 'vs/base/browser/dom'; @@ -766,7 +765,7 @@ export class DirtyDiffController extends Disposable implements IEditorContributi return; } - const data = e.target.detail as IMarginData; + const data = e.target.detail; const offsetLeftInGutter = (e.target.element as HTMLElement).offsetLeft; const gutterOffsetX = data.offsetX - offsetLeftInGutter; diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index f9fb536cf5e85..8e6c2c8e147f9 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -680,7 +680,7 @@ abstract class RunTestDecoration { /** * Called when the decoration is clicked on. */ - protected abstract getContextMenuActions(e: IEditorMouseEvent): IReference; + protected abstract getContextMenuActions(): IReference; protected defaultRun() { return this.testService.runTests({ @@ -697,7 +697,7 @@ abstract class RunTestDecoration { } private showContextMenu(e: IEditorMouseEvent) { - let actions = this.getContextMenuActions(e); + let actions = this.getContextMenuActions(); const editor = this.codeEditorService.listCodeEditors().find(e => e.getModel() === this.model); if (editor) { const contribution = editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID); @@ -844,7 +844,7 @@ class RunSingleTestDecoration extends RunTestDecoration implements ITestDecorati super([{ test, resultItem }], visible, model, codeEditorService, testService, contextMenuService, commandService, configurationService, testProfiles, contextKeyService, menuService); } - protected override getContextMenuActions(e: IEditorMouseEvent) { + protected override getContextMenuActions() { return this.getTestContextMenuActions(this.tests[0].test, this.tests[0].resultItem); } } From 05a83d831b840225ea0ca14bc7e52aa049e9d87b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 7 Jan 2022 16:40:36 +0100 Subject: [PATCH 1133/2210] Fix automatic tasks in wsl Fixes microsoft/vscode-remote-release#6102 --- .../contrib/tasks/browser/abstractTaskService.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 5d59b36ad2c65..581d17f807f58 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -578,13 +578,20 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } get hasTaskSystemInfo(): boolean { + // If there's a remoteAuthority, then we end up with 2 taskSystemInfos, + // one for each extension host. + if (this.environmentService.remoteAuthority) { + return this._taskSystemInfos.size > 1; + } return this._taskSystemInfos.size > 0; } public registerTaskSystem(key: string, info: TaskSystemInfo): void { if (!this._taskSystemInfos.has(key) || info.platform !== Platform.Platform.Web) { this._taskSystemInfos.set(key, info); - this._onDidChangeTaskSystemInfo.fire(); + if (this.hasTaskSystemInfo) { + this._onDidChangeTaskSystemInfo.fire(); + } } } From 58cfe97ebc28e3189e8bce23760a17e2756c73a5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 07:58:44 -0800 Subject: [PATCH 1134/2210] Treat -1 exit codes specially for pwsh --- .../terminal/browser/terminalInstance.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3c07008a32e3d..f9c868617908d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -712,11 +712,22 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (label.length === 0) { continue; } - const cwdDescription = cwd ? `cwd: ${cwd} ` : ''; - const exitCodeDescription = exitCode ? `exitCode: ${exitCode} ` : ''; + let description = ''; + if (cwd) { + description += `cwd: ${cwd} `; + } + if (exitCode) { + // Since you cannot get the last command's exit code on pwsh, just whether it failed + // or not, -1 is treated specially as simply failed + if (exitCode === -1) { + description += 'failed'; + } else { + description += `exitCode: ${exitCode}`; + } + } items.push({ label, - description: exitCodeDescription + cwdDescription, + description: description.trim(), detail: fromNow(timestamp, true), id: timestamp.toString() }); From 068bf3eef4be578a59e01b045872bc1579a6396c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 08:03:30 -0800 Subject: [PATCH 1135/2210] Simplify frequency increment --- .../browser/xterm/cognisantCommandTrackerAddon.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts index 30861d3e3bb66..732bbf43acaec 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -49,12 +49,8 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { switch (event.type) { case ShellIntegrationInfo.CurrentDir: { this._cwd = event.value; - const freq = this._cwds.get(this._cwd); - if (freq) { - this._cwds.set(this._cwd, freq + 1); - } else { - this._cwds.set(this._cwd, 1); - } + const freq = this._cwds.get(this._cwd) || 0; + this._cwds.set(this._cwd, freq + 1); this._onCwdChanged.fire(this._cwd); break; } From 0e8224b3d349590dcf3616c46157ba193c30b39d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 08:08:33 -0800 Subject: [PATCH 1136/2210] Don't pass capabilities to XtermTerminal --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- .../terminal/browser/xterm/cognisantCommandTrackerAddon.ts | 4 ++-- .../workbench/contrib/terminal/browser/xterm/xtermTerminal.ts | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index f9c868617908d..cd8446d5c2a2f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -567,7 +567,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { throw new Error('Terminal disposed of during xterm.js creation'); } - const xterm = this._instantiationService.createInstance(XtermTerminal, Terminal, this._configHelper, this._cols, this._rows, this.target || TerminalLocation.Panel, this.capabilities); + const xterm = this._instantiationService.createInstance(XtermTerminal, Terminal, this._configHelper, this._cols, this._rows, this.target || TerminalLocation.Panel); this.xterm = xterm; const lineDataEventAddon = new LineDataEventAddon(); this.xterm.raw.loadAddon(lineDataEventAddon); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts index 732bbf43acaec..1b32baf67e254 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -67,7 +67,7 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { // TODO: Leverage key events on Windows between CommandStart and Executed to ensure we have the correct line - // TODO: Only do on this on Windows backends + // TODO: Only do this on Windows backends // Check if the command line is the same as the previous command line or if the // start Y differs from the executed Y. This is to catch the conpty case where the // "rendering" of the shell integration sequences doesn't occur on the correct cell @@ -81,7 +81,7 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { // TODO: This does not yet work when the prompt line is wrapped this._currentCommand.command = this._terminal!.buffer.active.getLine(this._currentCommand.commandExecutedY)?.translateToString(true, this._currentCommand.commandStartX || 0); - // TODO: Only do on this on Windows backends + // TODO: Only do this on Windows backends // Something went wrong, try predict the prompt based on the shell. if (this._currentCommand.commandStartX === 0) { // TODO: Only do this on pwsh diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 175c1e2294145..71cf2ff0bab8d 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -80,7 +80,6 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { cols: number, rows: number, location: TerminalLocation, - capabilities: ProcessCapability[], @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, From b1aa9661d0338dd398b06441209fe84f4f9bafc6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 7 Jan 2022 17:18:52 +0100 Subject: [PATCH 1137/2210] tests - skip flaky notebook test --- .../vscode-api-tests/src/singlefolder-tests/notebook.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 2ff854913ef7e..808e056350c5b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -625,7 +625,7 @@ suite('Notebook API tests', function () { }); }); - test('multiple tabs: dirty + clean', async function () { + test.skip('multiple tabs: dirty + clean', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/140285 const notebook = await openRandomNotebookDocument(); await vscode.window.showNotebookDocument(notebook); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); From ee528ae9eb20f3c7e0e34b0ac86e3672ec48f497 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 7 Jan 2022 17:20:14 +0100 Subject: [PATCH 1138/2210] polish server cli new args: connection-token-file, file-watcher-polling. For #137658 --- src/vs/platform/environment/node/argv.ts | 34 +++++++++++-------- .../server/remoteExtensionHostAgentServer.ts | 33 +++++++++++++----- .../server/remoteFileSystemProviderServer.ts | 2 +- src/vs/server/serverEnvironmentService.ts | 14 ++++---- 4 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 1c120d191ac3b..38a4423d10efd 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -20,7 +20,7 @@ const helpCategories = { export interface Option { type: OptionType; alias?: string; - deprecates?: string; // old deprecated id + deprecates?: string[]; // old deprecated ids args?: string | string[]; description?: string; cat?: keyof typeof helpCategories; @@ -49,7 +49,7 @@ export const OPTIONS: OptionDescriptions> = { 'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") }, 'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") }, - 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, + 'extensions-dir': { type: 'string', deprecates: ['extensionHomePath'], cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, 'extensions-download-dir': { type: 'string' }, 'builtin-extensions-dir': { type: 'string' }, 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, @@ -69,12 +69,12 @@ export const OPTIONS: OptionDescriptions> = { 'no-cached-data': { type: 'boolean' }, 'prof-startup-prefix': { type: 'string' }, 'prof-v8-extensions': { type: 'boolean' }, - 'disable-extensions': { type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, + 'disable-extensions': { type: 'boolean', deprecates: ['disableExtensions'], cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") }, 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on', 'off'] }, - 'inspect-extensions': { type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, - 'inspect-brk-extensions': { type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, + 'inspect-extensions': { type: 'string', deprecates: ['debugPluginHost'], args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, + 'inspect-brk-extensions': { type: 'string', deprecates: ['debugBrkPluginHost'], args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, 'disable-gpu': { type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") }, 'ms-enable-electron-run-as-node': { type: 'boolean' }, 'max-memory': { type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes)."), args: 'memory' }, @@ -93,8 +93,8 @@ export const OPTIONS: OptionDescriptions> = { 'debugRenderer': { type: 'boolean' }, 'inspect-ptyhost': { type: 'string' }, 'inspect-brk-ptyhost': { type: 'string' }, - 'inspect-search': { type: 'string', deprecates: 'debugSearch' }, - 'inspect-brk-search': { type: 'string', deprecates: 'debugBrkSearch' }, + 'inspect-search': { type: 'string', deprecates: ['debugSearch'] }, + 'inspect-brk-search': { type: 'string', deprecates: ['debugBrkSearch'] }, 'export-default-configuration': { type: 'string' }, 'install-source': { type: 'string' }, 'driver': { type: 'string' }, @@ -177,12 +177,12 @@ export function parseArgs(args: string[], options: OptionDescriptions, err if (o.type === 'string' || o.type === 'string[]') { string.push(optionId); if (o.deprecates) { - string.push(o.deprecates); + string.push(...o.deprecates); } } else if (o.type === 'boolean') { boolean.push(optionId); if (o.deprecates) { - boolean.push(o.deprecates); + boolean.push(...o.deprecates); } } } @@ -204,14 +204,18 @@ export function parseArgs(args: string[], options: OptionDescriptions, err } let val = remainingArgs[optionId]; - if (o.deprecates && remainingArgs.hasOwnProperty(o.deprecates)) { - if (!val) { - val = remainingArgs[o.deprecates]; - if (val) { - errorReporter.onDeprecatedOption(o.deprecates, optionId); + if (o.deprecates) { + for (const deprecatedId of o.deprecates) { + if (remainingArgs.hasOwnProperty(deprecatedId)) { + if (!val) { + val = remainingArgs[deprecatedId]; + if (val) { + errorReporter.onDeprecatedOption(deprecatedId, optionId); + } + } + delete remainingArgs[deprecatedId]; } } - delete remainingArgs[o.deprecates]; } if (typeof val !== 'undefined') { diff --git a/src/vs/server/remoteExtensionHostAgentServer.ts b/src/vs/server/remoteExtensionHostAgentServer.ts index 84a63c2c461fb..209dd6521d3b6 100644 --- a/src/vs/server/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/remoteExtensionHostAgentServer.ts @@ -919,21 +919,36 @@ export class RemoteExtensionHostAgentServer extends Disposable { } } +const connectionTokenRegex = /^[0-9A-Za-z-]+$/; + function parseConnectionToken(args: ServerParsedArgs): { connectionToken: string; connectionTokenIsMandatory: boolean; } { - if (args['connection-secret']) { - if (args['connection-token']) { - console.warn(`Please do not use the argument '--connection-token' at the same time as '--connection-secret'.`); + const connectionToken = args['connection-token']; + const connectionTokenFile = args['connection-token-file']; + + if (connectionTokenFile) { + if (connectionToken) { + console.warn(`Please do not use the argument '--connection-token' at the same time as '--connection-token-file'.`); process.exit(1); } - let rawConnectionToken = fs.readFileSync(args['connection-secret']).toString(); - rawConnectionToken = rawConnectionToken.replace(/\r?\n$/, ''); - if (!/^[0-9A-Za-z\-]+$/.test(rawConnectionToken)) { - console.warn(`The secret defined in ${args['connection-secret']} does not adhere to the characters 0-9, a-z, A-Z or -.`); + try { + let rawConnectionToken = fs.readFileSync(connectionTokenFile).toString(); + rawConnectionToken = rawConnectionToken.replace(/\r?\n$/, ''); + if (!connectionTokenRegex.test(rawConnectionToken)) { + console.warn(`The connection token defined in '${connectionTokenFile} does not adhere to the characters 0-9, a-z, A-Z or -.`); + process.exit(1); + } + return { connectionToken: rawConnectionToken, connectionTokenIsMandatory: true }; + } catch (e) { + console.warn(`Unable to read the connection token file at '${connectionTokenFile}'.`); process.exit(1); } - return { connectionToken: rawConnectionToken, connectionTokenIsMandatory: true }; + } else { - return { connectionToken: args['connection-token'] || generateUuid(), connectionTokenIsMandatory: false }; + if (connectionToken !== undefined && !connectionTokenRegex.test(connectionToken)) { + console.warn(`The connection token '${connectionToken}' does not adhere to the characters 0-9, a-z, A-Z or -.`); + process.exit(1); + } + return { connectionToken: connectionToken || generateUuid(), connectionTokenIsMandatory: false }; } } diff --git a/src/vs/server/remoteFileSystemProviderServer.ts b/src/vs/server/remoteFileSystemProviderServer.ts index c4a4d33f7f980..b6e9892b61107 100644 --- a/src/vs/server/remoteFileSystemProviderServer.ts +++ b/src/vs/server/remoteFileSystemProviderServer.ts @@ -91,7 +91,7 @@ class SessionFileWatcher extends Disposable implements ISessionFileWatcher { } private getWatcherOptions(): IWatcherOptions | undefined { - const fileWatcherPolling = this.environmentService.args['fileWatcherPolling']; + const fileWatcherPolling = this.environmentService.args['file-watcher-polling']; if (fileWatcherPolling) { const segments = fileWatcherPolling.split(delimiter); const pollingInterval = Number(segments[0]); diff --git a/src/vs/server/serverEnvironmentService.ts b/src/vs/server/serverEnvironmentService.ts index a592f44381da6..040938b776a66 100644 --- a/src/vs/server/serverEnvironmentService.ts +++ b/src/vs/server/serverEnvironmentService.ts @@ -18,8 +18,8 @@ export const serverOptions: OptionDescriptions = { 'port': { type: 'string', cat: 'o', description: nls.localize('port', 'The port the server should listen to. If 0 is passed a random free port is picked. If a range in the format num-num is passed, a free port from the range is selected.') }, 'pick-port': { type: 'string' }, 'socket-path': { type: 'string', cat: 'o', description: nls.localize('socket-path', 'The path to a socket file for the server to listen to.') }, - 'connection-token': { type: 'string', cat: 'o', deprecates: 'connectionToken', description: nls.localize('connection-token', "A secret that must be included by the web client with all requests.") }, - 'connection-secret': { type: 'string', cat: 'o', description: nls.localize('connection-secret', "Path to file that contains the connection token. This will require that all incoming connections know the secret.") }, + 'connection-token': { type: 'string', cat: 'o', deprecates: ['connectionToken'], description: nls.localize('connection-token', "A secret that must be included with all requests.") }, + 'connection-token-file': { type: 'string', cat: 'o', deprecates: ['connection-secret', 'connectionTokenFile'], description: nls.localize('connection-token-file', "Path to a file that contains the connection token. This will require that all incoming connections know the secret.") }, 'disable-websocket-compression': { type: 'boolean' }, 'print-startup-performance': { type: 'boolean' }, 'print-ip-address': { type: 'boolean' }, @@ -30,7 +30,7 @@ export const serverOptions: OptionDescriptions = { 'user-data-dir': OPTIONS['user-data-dir'], 'driver': OPTIONS['driver'], 'disable-telemetry': OPTIONS['disable-telemetry'], - 'fileWatcherPolling': { type: 'string' }, + 'file-watcher-polling': { type: 'string', deprecates: ['fileWatcherPolling']}, 'log': OPTIONS['log'], 'logsPath': OPTIONS['logsPath'], 'force-disable-user-env': OPTIONS['force-disable-user-env'], @@ -95,7 +95,7 @@ export interface ServerParsedArgs { * By default, a UUID will be generated every time the server starts up. * * If the server is running on a multi-user system, then consider - * using `--connection-secret` which has the advantage that the token cannot + * using `--connection-token-file` which has the advantage that the token cannot * be seen by other users using `ps` or similar commands. */ 'connection-token'?: string; @@ -103,12 +103,12 @@ export interface ServerParsedArgs { * A path to a filename which will be read on startup. * Consider placing this file in a folder readable only by the same user (a `chmod 0700` directory). * - * The contents of the file will be used as the connectionToken. Use only `[0-9A-Z\-]` as contents in the file. + * The contents of the file will be used as the connection token. Use only `[0-9A-Z\-]` as contents in the file. * The file can optionally end in a `\n` which will be ignored. * * This secret must be communicated to any vscode instance via the resolver or embedder API. */ - 'connection-secret'?: string; + 'connection-token-file'?: string; 'disable-websocket-compression'?: boolean; @@ -124,7 +124,7 @@ export interface ServerParsedArgs { driver?: string; 'disable-telemetry'?: boolean; - fileWatcherPolling?: string; + 'file-watcher-polling'?: string; 'log'?: string; 'logsPath'?: string; From ce5d3510b1488e8c763bfddbfa02c62958135495 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 7 Jan 2022 17:30:32 +0100 Subject: [PATCH 1139/2210] Fixes #139201 by disabling highlighting invisible characters in markdown and text files. --- extensions/markdown-basics/package.json | 3 ++- src/vs/editor/common/languages/modesRegistry.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index f3b99f6606fbc..18466e393909b 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -90,7 +90,8 @@ ], "configurationDefaults": { "[markdown]": { - "editor.unicodeHighlight.ambiguousCharacters": false + "editor.unicodeHighlight.ambiguousCharacters": false, + "editor.unicodeHighlight.invisibleCharacters": false } } }, diff --git a/src/vs/editor/common/languages/modesRegistry.ts b/src/vs/editor/common/languages/modesRegistry.ts index d902fc9a1e421..380574fcc0b9f 100644 --- a/src/vs/editor/common/languages/modesRegistry.ts +++ b/src/vs/editor/common/languages/modesRegistry.ts @@ -86,7 +86,8 @@ Registry.as(ConfigurationExtensions.Configuration) .registerDefaultConfigurations([{ overrides: { '[plaintext]': { - 'editor.unicodeHighlight.ambiguousCharacters': false + 'editor.unicodeHighlight.ambiguousCharacters': false, + 'editor.unicodeHighlight.invisibleCharacters': false } } }]); From 728dadf64b49d8c8d04486b360261b20227793da Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 08:40:09 -0800 Subject: [PATCH 1140/2210] Remove shell integration plumbing between renderer and pty host --- src/vs/platform/terminal/common/terminal.ts | 17 +-------------- src/vs/platform/terminal/node/ptyService.ts | 21 ++++++------------- .../platform/terminal/node/terminalProcess.ts | 7 +------ .../contrib/terminal/browser/remotePty.ts | 3 +-- .../xterm/cognisantCommandTrackerAddon.ts | 3 ++- .../browser/xterm/shellIntegrationAddon.ts | 13 +++++++++++- .../terminal/browser/xterm/xtermTerminal.ts | 4 ++-- .../terminal/electron-sandbox/localPty.ts | 3 +-- 8 files changed, 26 insertions(+), 45 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 8a73f1fe86864..64ce77f2050d6 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -191,8 +191,7 @@ export const enum ProcessPropertyType { ShellType = 'shellType', HasChildProcesses = 'hasChildProcesses', ResolvedShellLaunchConfig = 'resolvedShellLaunchConfig', - OverrideDimensions = 'overrideDimensions', - Capability = 'capability' + OverrideDimensions = 'overrideDimensions' } export interface IProcessProperty { @@ -209,7 +208,6 @@ export interface IProcessPropertyMap { [ProcessPropertyType.HasChildProcesses]: boolean, [ProcessPropertyType.ResolvedShellLaunchConfig]: IShellLaunchConfig, [ProcessPropertyType.OverrideDimensions]: ITerminalDimensionsOverride | undefined - [ProcessPropertyType.Capability]: ProcessCapability | undefined } export interface IFixedTerminalDimensions { @@ -379,19 +377,6 @@ export interface TerminalCommand { exitCode?: number; } -export const enum ShellIntegrationInfo { - CurrentDir = 'CurrentDir', -} -export const enum ShellIntegrationInteraction { - PromptStart = 'PROMPT_START', - CommandStart = 'COMMAND_START', - CommandExecuted = 'COMMAND_EXECUTED', - CommandFinished = 'COMMAND_FINISHED' -} - - -export interface IShellChangeEvent { type: ShellIntegrationInfo | ShellIntegrationInteraction, value: string } - export interface IShellLaunchConfig { /** * The name of the terminal, if this is not set the name of the process will be used. diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 73ff18f32e239..ebbb3daaeaea7 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -201,7 +201,6 @@ export class PtyService extends Disposable implements IPtyService { persistentProcess.installAutoReply(e[0], e[1]); } }); - this._ptys.set(id, persistentProcess); return id; } @@ -502,9 +501,6 @@ export class PersistentTerminalProcess extends Disposable { unicodeVersion, reviveBuffer ); - this._serializer.onDidStartShellIntegration(() => { - this._terminalProcess.updateProperty(ProcessPropertyType.Capability, ProcessCapability.CommandCognisant); - }); this._fixedDimensions = fixedDimensions; this._orphanQuestionBarrier = null; @@ -729,9 +725,7 @@ export class PersistentTerminalProcess extends Disposable { class XtermSerializer implements ITerminalSerializer { private _xterm: XtermTerminal; private _unicodeAddon?: XtermUnicode11Addon; - private readonly _onDidStartShellIntegration = new Emitter(); - readonly onDidStartShellIntegration = this._onDidStartShellIntegration.event; - private _shellIntegration: boolean | undefined; + private _isShellIntegrationEnabled: boolean = false; constructor( cols: number, @@ -745,7 +739,7 @@ class XtermSerializer implements ITerminalSerializer { this._xterm.writeln(reviveBuffer); } this._xterm.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); - if (this._shellIntegration) { + if (this._isShellIntegrationEnabled) { this._xterm.writeln('\x1b033]133;E\x1b007'); } this.setUnicodeVersion(unicodeVersion); @@ -753,13 +747,11 @@ class XtermSerializer implements ITerminalSerializer { private _handleShellIntegration(data: string): boolean { const [command,] = data.split(';'); - switch (command) { - case 'E': - this._shellIntegration = true; - this._onDidStartShellIntegration.fire(); - default: - return false; + if (command === 'E') { + this._isShellIntegrationEnabled = true; + return true; } + return false; } handleData(data: string): void { @@ -847,5 +839,4 @@ export interface ITerminalSerializer { handleResize(cols: number, rows: number): void; generateReplayEvent(normalBufferOnly?: boolean): Promise; setUnicodeVersion?(version: '6' | '11'): void; - onDidStartShellIntegration: Event; } diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 0d20ee74f6573..1651653136213 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -84,8 +84,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined, - capability: ProcessCapability.CommandCognisant + overrideDimensions: undefined }; private static _lastKillOrStart = 0; private _exitCode: number | undefined; @@ -426,10 +425,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess async updateProperty(type: T, value: IProcessPropertyMap[T]): Promise { if (type === ProcessPropertyType.FixedDimensions) { this._properties.fixedDimensions = value as IProcessPropertyMap[ProcessPropertyType.FixedDimensions]; - } else if (type === ProcessPropertyType.Capability) { - if (!this.capabilities.find(c => c === value) && value) { - this.capabilities.push(value as ProcessCapability); - } } this._onDidChangeProperty.fire({ type, value }); } diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index df1c256180416..1f5a709a94124 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -35,8 +35,7 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined, - capability: undefined + overrideDimensions: undefined }; private _capabilities: ProcessCapability[] = []; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts index 1b32baf67e254..943672ceaec5f 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import type { Terminal, IMarker } from 'xterm'; -import { ShellIntegrationInfo, ShellIntegrationInteraction, TerminalCommand } from 'vs/platform/terminal/common/terminal'; +import { TerminalCommand } from 'vs/platform/terminal/common/terminal'; import { Emitter } from 'vs/base/common/event'; import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { ILogService } from 'vs/platform/log/common/log'; +import { ShellIntegrationInfo, ShellIntegrationInteraction } from 'vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon'; interface ICurrentPartialCommand { marker?: IMarker; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts index 2cc39b2346ad0..90bfe638409b4 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts @@ -7,7 +7,7 @@ import { ITerminalAddon, Terminal } from 'xterm'; import { IShellIntegration } from 'vs/workbench/contrib/terminal/common/terminal'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ProcessCapability, ShellIntegrationInfo, ShellIntegrationInteraction } from 'vs/platform/terminal/common/terminal'; +import { ProcessCapability } from 'vs/platform/terminal/common/terminal'; /** * Shell integration is a feature that enhances the terminal's understanding of what's happening @@ -59,6 +59,17 @@ const enum ShellIntegrationOscPt { EnableShellIntegration = 'E', } +export const enum ShellIntegrationInfo { + CurrentDir = 'CurrentDir', +} + +export const enum ShellIntegrationInteraction { + PromptStart = 'PROMPT_START', + CommandStart = 'COMMAND_START', + CommandExecuted = 'COMMAND_EXECUTED', + CommandFinished = 'COMMAND_FINISHED' +} + export class ShellIntegrationAddon extends Disposable implements IShellIntegration, ITerminalAddon { private _terminal?: Terminal; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 71cf2ff0bab8d..2268f0f6e45bb 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -12,7 +12,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { ProcessCapability, ShellIntegrationInteraction, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ProcessCapability, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ICommandTracker, ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -28,7 +28,7 @@ import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TERMINAL_FOREGROUND_COLOR, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR, ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { Color } from 'vs/base/common/color'; -import { ShellIntegrationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon'; +import { ShellIntegrationAddon, ShellIntegrationInteraction } from 'vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon'; import { CognisantCommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts index b584e01ca6e9d..84a3f8b1fb595 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -24,8 +24,7 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined, - capability: undefined + overrideDimensions: undefined }; private _capabilities: ProcessCapability[] = []; get capabilities(): ProcessCapability[] { return this._capabilities; } From a56b709d685a077f91baeb6e579fd1ed6c161708 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 08:40:24 -0800 Subject: [PATCH 1141/2210] Use write, not writeln when enabling shell integration --- src/vs/platform/terminal/node/ptyService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index ebbb3daaeaea7..0354d5e310865 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -740,7 +740,7 @@ class XtermSerializer implements ITerminalSerializer { } this._xterm.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); if (this._isShellIntegrationEnabled) { - this._xterm.writeln('\x1b033]133;E\x1b007'); + this._xterm.write('\x1b033]133;E\x1b007'); } this.setUnicodeVersion(unicodeVersion); } From 487b25dc193ba4768555eed04e0a8bbb6499f67b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 08:44:21 -0800 Subject: [PATCH 1142/2210] Only re-write shell integration enable when reviving the buffer --- src/vs/platform/terminal/node/ptyService.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 0354d5e310865..f879374cdb51a 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -501,7 +501,6 @@ export class PersistentTerminalProcess extends Disposable { unicodeVersion, reviveBuffer ); - this._fixedDimensions = fixedDimensions; this._orphanQuestionBarrier = null; this._orphanQuestionReplyTime = 0; @@ -737,11 +736,11 @@ class XtermSerializer implements ITerminalSerializer { this._xterm = new XtermTerminal({ cols, rows, scrollback }); if (reviveBuffer) { this._xterm.writeln(reviveBuffer); + if (this._isShellIntegrationEnabled) { + this._xterm.write('\x1b033]133;E\x1b007'); + } } this._xterm.parser.registerOscHandler(133, (data => this._handleShellIntegration(data))); - if (this._isShellIntegrationEnabled) { - this._xterm.write('\x1b033]133;E\x1b007'); - } this.setUnicodeVersion(unicodeVersion); } From 6985184b461e7d3768c77a1f61c6cdae549a4ca5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 08:47:17 -0800 Subject: [PATCH 1143/2210] Remove unwanted event --- src/vs/platform/terminal/node/terminalProcess.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 1651653136213..37356738a4eb5 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -426,7 +426,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess if (type === ProcessPropertyType.FixedDimensions) { this._properties.fixedDimensions = value as IProcessPropertyMap[ProcessPropertyType.FixedDimensions]; } - this._onDidChangeProperty.fire({ type, value }); } private _startWrite(): void { From f7cb722447c54ae5788362115c810a66a23c129a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 7 Jan 2022 09:56:37 -0800 Subject: [PATCH 1144/2210] Disable all notebook integration tests in web See #126371 --- .../src/singlefolder-tests/notebook.editor.test.ts | 2 +- .../vscode-api-tests/src/singlefolder-tests/notebook.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index 71577a1122b1e..82becc4210c45 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import * as utils from '../utils'; -suite('Notebook Editor', function () { +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Notebook Editor', function () { const contentSerializer = new class implements vscode.NotebookSerializer { deserializeNotebook() { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 808e056350c5b..ebc6b221e4540 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -136,7 +136,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { } }; -suite('Notebook API tests', function () { +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Notebook API tests', function () { const testDisposables: vscode.Disposable[] = []; const suiteDisposables: vscode.Disposable[] = []; From 3a5f509b99daad155d1d2539326b638c3a5e33ab Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Fri, 7 Jan 2022 13:01:18 -0500 Subject: [PATCH 1145/2210] Fixes #140291 --- .../preferences/browser/keyboardLayoutPicker.ts | 2 +- .../keybinding/browser/keyboardLayoutService.ts | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index 47bd05136bee9..610ef7e5cedbf 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -51,7 +51,7 @@ export class KeyboardLayoutPickerContribution extends Disposable implements IWor ); } - this._register(keyboardLayoutService.onDidChangeKeyboardLayout(() => { + this._register(this.keyboardLayoutService.onDidChangeKeyboardLayout(() => { let layout = this.keyboardLayoutService.getCurrentKeyboardLayout(); let layoutInfo = parseKeyboardLayoutDescription(layout); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts index 6d71a818bbeaf..a0578017f3679 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { KeymapInfo, IRawMixedKeyboardMapping, IKeymapInfo } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { DispatchConfig } from 'vs/platform/keyboardLayout/common/dispatchConfig'; @@ -511,7 +511,6 @@ export class BrowserKeyboardLayoutService extends Disposable implements IKeyboar private _userKeyboardLayout: UserKeyboardLayout; - private readonly layoutChangeListener = this._register(new MutableDisposable()); private readonly _factory: BrowserKeyboardMapperFactory; private _keyboardLayoutMode: string; @@ -529,7 +528,9 @@ export class BrowserKeyboardLayoutService extends Disposable implements IKeyboar this._keyboardLayoutMode = layout ?? 'autodetect'; this._factory = new BrowserKeyboardMapperFactory(notificationService, storageService, commandService); - this.registerKeyboardListener(); + this._register(this._factory.onDidChangeKeyboardMapper(() => { + this._onDidChangeKeyboardLayout.fire(); + })); if (layout && layout !== 'autodetect') { // set keyboard layout @@ -543,11 +544,9 @@ export class BrowserKeyboardLayoutService extends Disposable implements IKeyboar this._keyboardLayoutMode = layout; if (layout === 'autodetect') { - this.registerKeyboardListener(); this._factory.onKeyboardLayoutChanged(); } else { this._factory.setKeyboardLayout(layout); - this.layoutChangeListener.clear(); } } })); @@ -594,12 +593,6 @@ export class BrowserKeyboardLayoutService extends Disposable implements IKeyboar } } - registerKeyboardListener() { - this.layoutChangeListener.value = this._factory.onDidChangeKeyboardMapper(() => { - this._onDidChangeKeyboardLayout.fire(); - }); - } - getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { return this._factory.getKeyboardMapper(dispatchConfig); } From c6937c7c695854884340f0b7bf1c15a71879e072 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 10:29:21 -0800 Subject: [PATCH 1146/2210] Ensure word links are at least 1 character in length They are very likely not useful when 1 character Fixes #140230 --- .../contrib/terminal/browser/links/terminalLinkManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index 89eb2bfcf0520..736382705323f 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -106,7 +106,7 @@ export class TerminalLinkManager extends DisposableStore { if (unfilteredWordLinks) { wordLinks = []; for (const link of unfilteredWordLinks) { - if (!words.has(link.text)) { + if (!words.has(link.text) && link.text.length > 1) { wordLinks.push(link); words.add(link.text); } From c2740e2b484a0672e5e92a2602c81b3a0603ac0f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 10:35:19 -0800 Subject: [PATCH 1147/2210] Add pwsh shell integration script Part of #139397 --- build/gulpfile.vscode.js | 2 ++ .../browser/media/shellIntegration.ps1 | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index df089a67b9f88..60046cb2343ac 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -69,6 +69,8 @@ const vscodeResources = [ 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + 'out-build/vs/workbench/contrib/terminal/browser/media/*.ps1', + 'out-build/vs/workbench/contrib/terminal/browser/media/*.sh', 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', 'out-build/vs/**/markdown.css', 'out-build/vs/workbench/contrib/tasks/**/*.json', diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 new file mode 100644 index 0000000000000..be4a8cc7f1a54 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 @@ -0,0 +1,27 @@ +# --------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# --------------------------------------------------------------------------------------------- + +$Global:__VSCodeOriginalPrompt = (Get-Command Prompt).ScriptBlock + +function Global:__VSCode-Get-LastExitCode { + if ($? -eq $True) { + return "0" + } + # TODO: Should we just return a string instead? + return "-1" +} + +function Global:Prompt() { + return "`e]133;D;$(__VSCode-Get-LastExitCode)`u{7}`e]133;A`u{7}`e]1337;CurrentDir=$(Get-Location)`u{7}$(Invoke-Command -ScriptBlock $Global:__VSCodeOriginalPrompt)`e]133;B`u{7}" +} + +# TODO: Gracefully fallback when PSReadLine is not loaded +function Global:PSConsoleHostReadLine { + [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($Host.Runspace, $ExecutionContext) + # Write command executed sequence directly to Console to avoid the new line from Write-Host + [Console]::Write("`e]133;C`u{7}") +} + +Write-Output "`e]133;E`u{7}" From 635a2f6b5f9cb4e67d287cf65b315bfbedca541b Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 7 Jan 2022 12:52:04 -0600 Subject: [PATCH 1148/2210] fix non-windows command tracking and add output button (#140290) --- src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/browser/terminalInstance.ts | 34 ++++++++++++++----- .../xterm/cognisantCommandTrackerAddon.ts | 31 +++++++++++++---- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 64ce77f2050d6..09272651552b7 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -373,6 +373,7 @@ export interface IHeartbeatService { export interface TerminalCommand { command: string; timestamp: number; + getOutput: () => string | undefined; cwd?: string; exitCode?: number; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index cd8446d5c2a2f..718e566eb2dbd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -21,7 +21,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager, ProcessState, TERMINAL_VIEW_ID, INavigationMode, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, ITerminalProfileResolverService, TerminalCommandId, ITerminalBackend } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; @@ -38,13 +38,13 @@ import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTy import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalLocation, ProcessPropertyType, ProcessCapability, IProcessPropertyMap, WindowsShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalLocation, ProcessPropertyType, ProcessCapability, IProcessPropertyMap, WindowsShellType, TerminalCommand } from 'vs/platform/terminal/common/terminal'; import { IProductService } from 'vs/platform/product/common/productService'; import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { AutoOpenBarrier, Promises } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { ITerminalStatusList, TerminalStatus, TerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; -import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { isMacintosh, isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -69,6 +69,7 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work import { isFirefox } from 'vs/base/browser/browser'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; import { fromNow } from 'vs/base/common/date'; +import { ICommandService } from 'vs/platform/commands/common/commands'; const enum Constants { /** @@ -323,7 +324,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IEditorService private readonly _editorService: IEditorService, - @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService + @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService, + @ICommandService private readonly _commandService: ICommandService ) { super(); @@ -703,10 +705,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (!commands || !this.xterm) { return; } - type Item = IQuickPickItem; + type Item = IQuickPickItem & { command?: TerminalCommand }; const items: Item[] = []; if (type === 'command') { - for (const { command, timestamp, cwd, exitCode } of commands) { + for (const { command, timestamp, cwd, exitCode, getOutput } of commands) { // trim off any whitespace and/or line endings const label = command.trim(); if (label.length === 0) { @@ -725,11 +727,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { description += `exitCode: ${exitCode}`; } } + const buttons: IQuickInputButton[] = [{ + iconClass: ThemeIcon.asClassName(Codicon.output), + tooltip: nls.localize('viewCommandOutput', "View Command Output"), + alwaysVisible: true + }]; items.push({ label, description: description.trim(), detail: fromNow(timestamp, true), - id: timestamp.toString() + id: timestamp.toString(), + command: { command, timestamp, cwd, exitCode, getOutput }, + buttons }); } } else { @@ -738,7 +747,16 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { items.push({ label }); } } - const result = await this._quickInputService.pick(items.reverse(), {}); + const result = await this._quickInputService.pick(items.reverse(), { + onDidTriggerItemButton: (async e => { + const output = e.item.command?.getOutput(); + if (output) { + await this._clipboardService.writeText(output); + await this._commandService.executeCommand('workbench.action.files.newUntitledFile'); + await this._commandService.executeCommand('editor.action.clipboardPasteAction'); + } + }) + }); if (result) { this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, true); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts index 943672ceaec5f..55b9a5424b089 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/cognisantCommandTrackerAddon.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Terminal, IMarker } from 'xterm'; +import type { Terminal, IMarker, IBuffer } from 'xterm'; import { TerminalCommand } from 'vs/platform/terminal/common/terminal'; import { Emitter } from 'vs/base/common/event'; import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/xterm/commandTrackerAddon'; import { ILogService } from 'vs/platform/log/common/log'; import { ShellIntegrationInfo, ShellIntegrationInteraction } from 'vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon'; +import { isWindows } from 'vs/base/common/platform'; interface ICurrentPartialCommand { marker?: IMarker; @@ -64,6 +65,11 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { this._currentCommand.marker = this._terminal.registerMarker(0); break; case ShellIntegrationInteraction.CommandExecuted: + // TODO: Make sure this only runs on Windows backends (not frontends) + if (!isWindows && this._currentCommand.marker && this._currentCommand.commandStartX) { + this._currentCommand.command = this._terminal.buffer.active.getLine(this._currentCommand.marker.line)?.translateToString().substring(this._currentCommand.commandStartX); + break; + } this._currentCommand.commandExecutedY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY; // TODO: Leverage key events on Windows between CommandStart and Executed to ensure we have the correct line @@ -100,18 +106,21 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { } } break; - case ShellIntegrationInteraction.CommandFinished: - this._logService.trace('Terminal Command Finished', this._currentCommand.command); + case ShellIntegrationInteraction.CommandFinished: { + const command = this._currentCommand.command; + this._logService.trace('Terminal Command Finished', command); this._exitCode = Number.parseInt(event.value); if (!this._currentCommand.marker?.line || !this._terminal.buffer.active) { break; } - if (this._currentCommand.command && !this._currentCommand.command.startsWith('\\') && this._currentCommand.command !== '') { + if (command && !command.startsWith('\\') && command !== '') { + const buffer = this._terminal.buffer.active; this._commands.push({ - command: this._currentCommand.command, + command, timestamp: Date.now(), cwd: this._cwd, - exitCode: this._exitCode + exitCode: this._exitCode, + getOutput: () => getOutputForCommand(this._currentCommand.previousCommandMarker!.line! + 1, this._currentCommand.marker!.line!, buffer) }); } @@ -119,7 +128,7 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { this._currentCommand.previousCommandMarker = this._currentCommand.marker; this._currentCommand.marker = undefined; break; - default: + } default: return; } } @@ -137,3 +146,11 @@ export class CognisantCommandTrackerAddon extends CommandTrackerAddon { return cwds; } } + +function getOutputForCommand(startLine: number, endLine: number, buffer: IBuffer): string | undefined { + let output = ''; + for (let i = startLine; i < endLine; i++) { + output += buffer.getLine(i)?.translateToString() + '\n'; + } + return output === '' ? undefined : output; +} From 9867820ed97a37ff332d1dac0dded490b6618906 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 7 Jan 2022 20:14:19 +0100 Subject: [PATCH 1149/2210] Simplify `ContentHoverController` --- src/vs/editor/contrib/hover/contentHover.ts | 188 +++++++++--------- .../contrib/inlayHints/inlayHintsHover.ts | 4 +- 2 files changed, 98 insertions(+), 94 deletions(-) diff --git a/src/vs/editor/contrib/hover/contentHover.ts b/src/vs/editor/contrib/hover/contentHover.ts index 3c255f0de423f..f7536f5d50a8d 100644 --- a/src/vs/editor/contrib/hover/contentHover.ts +++ b/src/vs/editor/contrib/hover/contentHover.ts @@ -8,13 +8,13 @@ import { HoverAction, HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Constants } from 'vs/base/common/uint'; import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IModelDecoration } from 'vs/editor/common/model'; +import { IModelDecoration, IModelDeltaDecoration } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { TokenizationRegistry } from 'vs/editor/common/languages'; import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget'; @@ -32,6 +32,8 @@ import { UnicodeHighlighterHoverParticipant } from 'vs/editor/contrib/unicodeHig import { AsyncIterableObject } from 'vs/base/common/async'; import { InlayHintsHover } from 'vs/editor/contrib/inlayHints/inlayHintsHover'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { Emitter } from 'vs/base/common/event'; +import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents'; const $ = dom.$; @@ -39,15 +41,12 @@ export class ContentHoverController extends Disposable { private readonly _participants: IEditorHoverParticipant[]; private readonly _widget: ContentHoverWidget; + private readonly _decorationsChangerListener = this._register(new EditorDecorationsChangerListener(this._editor)); private _messages: IHoverPart[]; private _messagesAreComplete: boolean; private readonly _computer: ContentHoverComputer; private readonly _hoverOperation: HoverOperation; - private _highlightDecorations: string[]; - private _isChangingDecorations: boolean; - private _shouldFocus: boolean; - private _renderDisposable: DisposableStore | null; constructor( private readonly _editor: ICodeEditor, @@ -69,16 +68,12 @@ export class ContentHoverController extends Disposable { this._messages = []; this._messagesAreComplete = false; this._computer = new ContentHoverComputer(this._editor, this._participants); - this._highlightDecorations = []; - this._isChangingDecorations = false; - this._shouldFocus = false; - this._renderDisposable = null; this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer)); this._register(this._hoverOperation.onResult((result) => { this._withResult(result.value, result.isComplete, result.hasLoadingMessage); })); - this._register(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); + this._register(this._decorationsChangerListener.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); this._register(dom.addStandardDisposableListener(this._widget.getDomNode(), 'keydown', (e) => { if (e.equals(KeyCode.Escape)) { this.hide(); @@ -92,22 +87,16 @@ export class ContentHoverController extends Disposable { })); } - private _shouldShowAt(mouseEvent: IEditorMouseEvent): boolean { - const target = mouseEvent.target; - if (target.type === MouseTargetType.CONTENT_TEXT) { - return true; - } + private _onModelDecorationsChanged(): void { + if (this._widget.position) { + // The decorations have changed and the hover is visible, + // we need to recompute the displayed text + this._hoverOperation.cancel(); - if (target.type === MouseTargetType.CONTENT_EMPTY) { - const epsilon = this._editor.getOption(EditorOption.fontInfo).typicalHalfwidthCharacterWidth / 2; - const data = target.detail; - if (data && !data.isAfterLines && typeof data.horizontalDistanceToText === 'number' && data.horizontalDistanceToText < epsilon) { - // Let hover kick in even when the mouse is technically in the empty area after a line, given the distance is small enough - return true; + if (!this._widget.colorPicker) { // TODO@Michel ensure that displayed text for other decorations is computed even if color picker is in place + this._hoverOperation.start(HoverStartMode.Delayed); } } - - return false; } public maybeShowAt(mouseEvent: IEditorMouseEvent): boolean { @@ -122,8 +111,18 @@ export class ContentHoverController extends Disposable { } } - if (this._shouldShowAt(mouseEvent) && mouseEvent.target.range) { - anchorCandidates.push(new HoverRangeAnchor(0, mouseEvent.target.range)); + const target = mouseEvent.target; + + if (target.type === MouseTargetType.CONTENT_TEXT) { + anchorCandidates.push(new HoverRangeAnchor(0, target.range)); + } + + if (target.type === MouseTargetType.CONTENT_EMPTY) { + const epsilon = this._editor.getOption(EditorOption.fontInfo).typicalHalfwidthCharacterWidth / 2; + if (!target.detail.isAfterLines && typeof target.detail.horizontalDistanceToText === 'number' && target.detail.horizontalDistanceToText < epsilon) { + // Let hover kick in even when the mouse is technically in the empty area after a line, given the distance is small enough + anchorCandidates.push(new HoverRangeAnchor(0, target.range)); + } } if (anchorCandidates.length === 0) { @@ -132,25 +131,9 @@ export class ContentHoverController extends Disposable { anchorCandidates.sort((a, b) => b.priority - a.priority); this._startShowingAt(anchorCandidates[0], HoverStartMode.Delayed, false); - return true; } - private _onModelDecorationsChanged(): void { - if (this._isChangingDecorations) { - return; - } - if (this._widget.position) { - // The decorations have changed and the hover is visible, - // we need to recompute the displayed text - this._hoverOperation.cancel(); - - if (!this._widget.colorPicker) { // TODO@Michel ensure that displayed text for other decorations is computed even if color picker is in place - this._hoverOperation.start(HoverStartMode.Delayed); - } - } - } - public startShowingAtRange(range: Range, mode: HoverStartMode, focus: boolean): void { this._startShowingAt(new HoverRangeAnchor(0, range), mode, focus); } @@ -183,7 +166,7 @@ export class ContentHoverController extends Disposable { } this._computer.anchor = anchor; - this._shouldFocus = focus; + this._computer.shouldFocus = focus; this._hoverOperation.start(mode); } @@ -192,14 +175,6 @@ export class ContentHoverController extends Disposable { this._hoverOperation.cancel(); this._widget.hide(); - - this._isChangingDecorations = true; - this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, []); - this._isChangingDecorations = false; - if (this._renderDisposable) { - this._renderDisposable.dispose(); - this._renderDisposable = null; - } } public isColorPickerVisible(): boolean { @@ -232,31 +207,20 @@ export class ContentHoverController extends Disposable { } private _renderMessages(anchor: HoverAnchor, messages: IHoverPart[]): void { - if (this._renderDisposable) { - this._renderDisposable.dispose(); - this._renderDisposable = null; - } - // update column from which to show let renderColumn = Constants.MAX_SAFE_SMALL_INTEGER; let highlightRange: Range = messages[0].range; let forceShowAtRange: Range | null = null; - const groupedHoverParts = new Map(); for (const msg of messages) { renderColumn = Math.min(renderColumn, msg.range.startColumn); highlightRange = Range.plusRange(highlightRange, msg.range); if (msg.forceShowAtRange) { forceShowAtRange = msg.range; } - - if (!groupedHoverParts.has(msg.owner)) { - groupedHoverParts.set(msg.owner, []); - } - groupedHoverParts.get(msg.owner)!.push(msg); } - this._renderDisposable = new DisposableStore(); - const statusBar = this._renderDisposable.add(new EditorHoverStatusBar(this._keybindingService)); + const disposables = new DisposableStore(); + const statusBar = disposables.add(new EditorHoverStatusBar(this._keybindingService)); const fragment = document.createDocumentFragment(); let colorPicker: ColorPickerWidget | null = null; @@ -275,31 +239,37 @@ export class ContentHoverController extends Disposable { }; for (const participant of this._participants) { - if (groupedHoverParts.has(participant)) { - const participantHoverParts = groupedHoverParts.get(participant)!; - this._renderDisposable.add(participant.renderHoverParts(context, participantHoverParts)); + const hoverParts = messages.filter(msg => msg.owner === participant); + if (hoverParts.length > 0) { + disposables.add(participant.renderHoverParts(context, hoverParts)); } } if (statusBar.hasContent) { fragment.appendChild(statusBar.hoverElement); } - // show - if (fragment.hasChildNodes()) { - if (forceShowAtRange) { - this._widget.showAt(fragment, colorPicker, forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus); - } else { - this._widget.showAt(fragment, colorPicker, new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus); + if (highlightRange) { + const highlightDecorations = this._decorationsChangerListener.deltaDecorations([], [{ + range: highlightRange, + options: ContentHoverController._DECORATION_OPTIONS + }]); + disposables.add(toDisposable(() => { + this._decorationsChangerListener.deltaDecorations(highlightDecorations, []); + })); } - } - this._isChangingDecorations = true; - this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, highlightRange ? [{ - range: highlightRange, - options: ContentHoverController._DECORATION_OPTIONS - }] : []); - this._isChangingDecorations = false; + this._widget.showAt(fragment, new ContentHoverVisibleData( + colorPicker, + forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchor.range.startLineNumber, renderColumn), + forceShowAtRange ? forceShowAtRange : highlightRange, + this._editor.getOption(EditorOption.hover).above, + this._computer.shouldFocus, + disposables + )); + } else { + disposables.dispose(); + } } private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ @@ -308,6 +278,35 @@ export class ContentHoverController extends Disposable { }); } +class EditorDecorationsChangerListener extends Disposable { + + private readonly _onDidChangeModelDecorations = this._register(new Emitter()); + public readonly onDidChangeModelDecorations = this._onDidChangeModelDecorations.event; + + private _isChangingDecorations: boolean = false; + + constructor( + private readonly _editor: ICodeEditor + ) { + super(); + this._register(this._editor.onDidChangeModelDecorations((e) => { + if (this._isChangingDecorations) { + return; + } + this._onDidChangeModelDecorations.fire(e); + })); + } + + public deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { + try { + this._isChangingDecorations = true; + return this._editor.deltaDecorations(oldDecorations, newDecorations); + } finally { + this._isChangingDecorations = false; + } + } +} + class ContentHoverVisibleData { constructor( public readonly colorPicker: ColorPickerWidget | null, @@ -315,6 +314,7 @@ class ContentHoverVisibleData { public readonly showAtRange: Range | null, public readonly preferAbove: boolean, public readonly stoleFocus: boolean, + public readonly disposables: DisposableStore ) { } } @@ -363,6 +363,9 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { public override dispose(): void { this._editor.removeContentWidget(this); + if (this._visibleData) { + this._visibleData.disposables.dispose(); + } super.dispose(); } @@ -395,6 +398,9 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { } private _setVisibleData(visibleData: ContentHoverVisibleData | null): void { + if (this._visibleData) { + this._visibleData.disposables.dispose(); + } this._visibleData = visibleData; this._hoverVisibleKey.set(!!this._visibleData); this._hover.containerDomNode.classList.toggle('hidden', !this._visibleData); @@ -415,8 +421,8 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { codeClasses.forEach(node => this._editor.applyFontInfo(node)); } - public showAt(node: DocumentFragment, colorPicker: ColorPickerWidget | null, position: Position, range: Range | null, focus: boolean): void { - this._setVisibleData(new ContentHoverVisibleData(colorPicker, position, range, this._editor.getOption(EditorOption.hover).above, focus)); + public showAt(node: DocumentFragment, visibleData: ContentHoverVisibleData): void { + this._setVisibleData(visibleData); this._hover.contentsDomNode.textContent = ''; this._hover.contentsDomNode.appendChild(node); @@ -428,11 +434,11 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { // Simply force a synchronous render on the editor // such that the widget does not really render with left = '0px' this._editor.render(); - if (focus) { + if (visibleData.stoleFocus) { this._hover.containerDomNode.focus(); } - if (colorPicker) { - colorPicker.layout(); + if (visibleData.colorPicker) { + visibleData.colorPicker.layout(); } } @@ -491,14 +497,12 @@ class EditorHoverStatusBar extends Disposable implements IEditorHoverStatusBar { class ContentHoverComputer implements IHoverComputer { private _anchor: HoverAnchor | null = null; + public get anchor(): HoverAnchor | null { return this._anchor; } + public set anchor(value: HoverAnchor | null) { this._anchor = value; } - public get anchor(): HoverAnchor | null { - return this._anchor; - } - - public set anchor(value: HoverAnchor | null) { - this._anchor = value; - } + private _shouldFocus: boolean = false; + public get shouldFocus(): boolean { return this._shouldFocus; } + public set shouldFocus(value: boolean) { this._shouldFocus = value; } constructor( private readonly _editor: ICodeEditor, diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts index 583c3773a941c..5cad01c101ff7 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts @@ -10,7 +10,7 @@ import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrow import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; -import { HoverAnchor, HoverForeignElementAnchor } from 'vs/editor/contrib/hover/hoverTypes'; +import { HoverAnchor, HoverForeignElementAnchor, IEditorHoverParticipant } from 'vs/editor/contrib/hover/hoverTypes'; import { MarkdownHover, MarkdownHoverParticipant } from 'vs/editor/contrib/hover/markdownHoverParticipant'; import { InlayHintItem } from 'vs/editor/contrib/inlayHints/inlayHints'; import { InlayHintLabelPart, InlayHintsController } from 'vs/editor/contrib/inlayHints/inlayHintsController'; @@ -22,7 +22,7 @@ class InlayHintsHoverAnchor extends HoverForeignElementAnchor { } } -export class InlayHintsHover extends MarkdownHoverParticipant { +export class InlayHintsHover extends MarkdownHoverParticipant implements IEditorHoverParticipant { suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { const controller = InlayHintsController.get(this._editor); From 2586299c42490a8ed6dff96ed731d44fcbe5b20e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 7 Jan 2022 20:38:54 +0100 Subject: [PATCH 1150/2210] experiment with InlayHintLabelPart and source location as "anchor action", https://github.com/microsoft/vscode/issues/129528 --- src/vs/editor/common/languages.ts | 25 ++++- .../inlayHints/inlayHintsController.ts | 89 +++++++++++------- .../contrib/inlayHints/inlayHintsHover.ts | 91 +++++++++++++++---- src/vs/monaco.d.ts | 8 +- .../api/browser/mainThreadLanguageFeatures.ts | 5 +- .../workbench/api/common/extHost.api.impl.ts | 1 + .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostApiCommands.ts | 4 +- .../api/common/extHostLanguageFeatures.ts | 67 ++++++++++---- .../api/common/extHostTypeConverters.ts | 29 +++--- src/vs/workbench/api/common/extHostTypes.ts | 23 ++++- .../browser/api/extHostApiCommands.test.ts | 8 +- .../vscode.proposed.inlayHints.d.ts | 15 ++- 13 files changed, 267 insertions(+), 100 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index bf79ed3455b17..f670ffeab44c4 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1541,6 +1541,23 @@ export interface Command { arguments?: any[]; } +/** + * @internal + */ +export namespace Command { + + /** + * @internal + */ + export function is(obj: any): obj is Command { + if (!obj || typeof obj !== 'object') { + return false; + } + return typeof (obj).id === 'string' && + typeof (obj).title === 'string'; + } +} + /** * @internal */ @@ -1721,8 +1738,14 @@ export enum InlayHintKind { Parameter = 2, } -export interface InlayHint { +export interface InlayHintLabelPart { label: string; + collapsible?: boolean; + action?: Command | Location +} + +export interface InlayHint { + label: string | InlayHintLabelPart[]; tooltip?: string | IMarkdownString position: IPosition; kind: InlayHintKind; diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 2bcd9945105b8..c6ec5db0ad057 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -6,7 +6,6 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { parseLinkedText } from 'vs/base/common/linkedText'; import { LRUCache } from 'vs/base/common/map'; import { IRange } from 'vs/base/common/range'; import { assertType } from 'vs/base/common/types'; @@ -17,15 +16,15 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { InlayHint, InlayHintKind, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; +import * as languages from 'vs/editor/common/languages'; import { LanguageFeatureRequestDelays } from 'vs/editor/common/languages/languageFeatureRegistry'; import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ClickLinkGesture } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; import { InlayHintAnchor, InlayHintItem, InlayHintsFragments } from 'vs/editor/contrib/inlayHints/inlayHints'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -52,7 +51,16 @@ class InlayHintsCache { } export class InlayHintLabelPart { - constructor(readonly item: InlayHintItem, readonly index: number, readonly href?: string) { } + constructor(readonly item: InlayHintItem, readonly index: number) { } + + get part() { + const label = this.item.hint.label; + if (typeof label === 'string') { + return { label }; + } else { + return label[this.index]; + } + } } export class InlayHintsController implements IEditorContribution { @@ -65,7 +73,7 @@ export class InlayHintsController implements IEditorContribution { private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); - private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 25, 500); + private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(languages.InlayHintsProviderRegistry, 25, 500); private readonly _cache = new InlayHintsCache(); private readonly _decorationsMetadata = new Map(); private readonly _ruleFactory = new DynamicCssRules(this._editor); @@ -74,9 +82,10 @@ export class InlayHintsController implements IEditorContribution { constructor( private readonly _editor: ICodeEditor, - @IOpenerService private readonly _openerService: IOpenerService, + @ICommandService private readonly _commandService: ICommandService, + @INotificationService private readonly _notificationService: INotificationService, ) { - this._disposables.add(InlayHintsProviderRegistry.onDidChange(() => this._update())); + this._disposables.add(languages.InlayHintsProviderRegistry.onDidChange(() => this._update())); this._disposables.add(_editor.onDidChangeModel(() => this._update())); this._disposables.add(_editor.onDidChangeModelLanguage(() => this._update())); this._disposables.add(_editor.onDidChangeConfiguration(e => { @@ -102,7 +111,7 @@ export class InlayHintsController implements IEditorContribution { } const model = this._editor.getModel(); - if (!model || !InlayHintsProviderRegistry.has(model)) { + if (!model || !languages.InlayHintsProviderRegistry.has(model)) { return; } @@ -156,7 +165,18 @@ export class InlayHintsController implements IEditorContribution { } const model = this._editor.getModel()!; const options = mouseEvent.target.detail.injectedText?.options; - if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { + + if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart)) { + removeHighlight(); + return; + } + + // kick-off resolve whenever the mouse is over inlay hints + // todo@jrieken CANCEL for real! + options.attachedData.item.resolve(CancellationToken.None); + + // render link => when the modifier is pressed and when there is an action + if (mouseEvent.hasTriggerModifier && options.attachedData.part.action) { this._activeInlayHintPart = options.attachedData; const lineNumber = this._activeInlayHintPart.item.hint.position.lineNumber; @@ -179,9 +199,16 @@ export class InlayHintsController implements IEditorContribution { if (e.target.type !== MouseTargetType.CONTENT_TEXT || !e.hasTriggerModifier) { return; } - const options = e.target.detail.injectedText?.options; - if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { - this._openerService.open(options.attachedData.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); + const options = e.target.detail?.injectedText?.options; + if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.part.action) { + const part = options.attachedData.part; + if (languages.Command.is(part.action)) { + this._commandService.executeCommand(part.action.id, ...(part.action.arguments ?? [])).catch(err => this._notificationService.error(err)); + + } else if (part.action) { + console.log('TODO', part.action); + // todo@jrieken IMPLEMENT THIS + } } }); return gesture; @@ -233,17 +260,20 @@ export class InlayHintsController implements IEditorContribution { for (const item of items) { + const parts: languages.InlayHintLabelPart[] = typeof item.hint.label === 'string' + ? [{ label: item.hint.label }] + : item.hint.label; + // text w/ links - const { nodes } = parseLinkedText(item.hint.label); + const marginBefore = item.hint.whitespaceBefore ? (fontSize / 3) | 0 : 0; const marginAfter = item.hint.whitespaceAfter ? (fontSize / 3) | 0 : 0; - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; const isFirst = i === 0; - const isLast = i === nodes.length - 1; - const isLink = typeof node === 'object'; + const isLast = i === parts.length - 1; const cssProperties: CssProperties = { fontSize: `${fontSize}px`, @@ -253,14 +283,11 @@ export class InlayHintsController implements IEditorContribution { this._fillInColors(cssProperties, item.hint); - if (isLink) { + if (part.action && this._activeInlayHintPart?.item === item && this._activeInlayHintPart.index === i) { + // active link! cssProperties.textDecoration = 'underline'; - - if (this._activeInlayHintPart?.item === item && this._activeInlayHintPart.index === i && this._activeInlayHintPart.href === node.href) { - // active link! - cssProperties.cursor = 'pointer'; - cssProperties.color = themeColorFromId(colors.editorActiveLinkForeground); - } + cssProperties.cursor = 'pointer'; + cssProperties.color = themeColorFromId(colors.editorActiveLinkForeground); } if (isFirst && isLast) { @@ -291,10 +318,10 @@ export class InlayHintsController implements IEditorContribution { range: item.anchor.range, options: { [item.anchor.direction]: { - content: fixSpace(isLink ? node.label : node), + content: fixSpace(part.label), inlineClassNameAffectsLetterSpacing: true, inlineClassName: classNameRef.className, - attachedData: new InlayHintLabelPart(item, i, isLink ? node.href : undefined) + attachedData: new InlayHintLabelPart(item, i) } as InjectedTextOptions, description: 'InlayHint', showIfCollapsed: !item.anchor.usesWordRange, @@ -330,11 +357,11 @@ export class InlayHintsController implements IEditorContribution { } } - private _fillInColors(props: CssProperties, hint: InlayHint): void { - if (hint.kind === InlayHintKind.Parameter) { + private _fillInColors(props: CssProperties, hint: languages.InlayHint): void { + if (hint.kind === languages.InlayHintKind.Parameter) { props.backgroundColor = themeColorFromId(colors.editorInlayHintParameterBackground); props.color = themeColorFromId(colors.editorInlayHintParameterForeground); - } else if (hint.kind === InlayHintKind.Type) { + } else if (hint.kind === languages.InlayHintKind.Type) { props.backgroundColor = themeColorFromId(colors.editorInlayHintTypeBackground); props.color = themeColorFromId(colors.editorInlayHintTypeForeground); } else { @@ -373,7 +400,7 @@ function fixSpace(str: string): string { registerEditorContribution(InlayHintsController.ID, InlayHintsController); -CommandsRegistry.registerCommand('_executeInlayHintProvider', async (accessor, ...args: [URI, IRange]): Promise => { +CommandsRegistry.registerCommand('_executeInlayHintProvider', async (accessor, ...args: [URI, IRange]): Promise => { const [uri, range] = args; assertType(URI.isUri(uri)); diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts index 5cad01c101ff7..632bef17f4242 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts @@ -5,25 +5,39 @@ import { AsyncIterableObject } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { Range } from 'vs/editor/common/core/range'; +import { IMarkdownString, isEmptyMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; +import { Command, HoverProviderRegistry } from 'vs/editor/common/languages'; import { IModelDecoration } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { HoverAnchor, HoverForeignElementAnchor, IEditorHoverParticipant } from 'vs/editor/contrib/hover/hoverTypes'; +import { ILanguageService } from 'vs/editor/common/services/language'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { getHover } from 'vs/editor/contrib/hover/getHover'; import { MarkdownHover, MarkdownHoverParticipant } from 'vs/editor/contrib/hover/markdownHoverParticipant'; -import { InlayHintItem } from 'vs/editor/contrib/inlayHints/inlayHints'; import { InlayHintLabelPart, InlayHintsController } from 'vs/editor/contrib/inlayHints/inlayHintsController'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { - - constructor(readonly item: InlayHintItem, owner: InlayHintsHover) { - super(10, owner, Range.fromPositions(item.hint.position)); + constructor(readonly part: InlayHintLabelPart, owner: InlayHintsHover) { + super(10, owner, part.item.anchor.range); } } export class InlayHintsHover extends MarkdownHoverParticipant implements IEditorHoverParticipant { + constructor( + editor: ICodeEditor, + @ILanguageService languageService: ILanguageService, + @IOpenerService openerService: IOpenerService, + @IConfigurationService configurationService: IConfigurationService, + @ITextModelService private readonly _resolverService: ITextModelService + ) { + super(editor, languageService, openerService, configurationService); + } + suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { const controller = InlayHintsController.get(this._editor); if (!controller) { @@ -36,7 +50,7 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart)) { return null; } - return new InlayHintsHoverAnchor(options.attachedData.item, this); + return new InlayHintsHoverAnchor(options.attachedData, this); } override computeSync(): MarkdownHover[] { @@ -48,18 +62,57 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor return AsyncIterableObject.EMPTY; } - const { item } = anchor; - return AsyncIterableObject.fromPromise(item.resolve(token).then(() => { - if (!item.hint.tooltip) { - return []; + return new AsyncIterableObject(async executor => { + + const { part } = anchor; + await part.item.resolve(token); + + if (token.isCancellationRequested) { + return; } - let contents: IMarkdownString; - if (typeof item.hint.tooltip === 'string') { - contents = new MarkdownString().appendText(item.hint.tooltip); - } else { - contents = item.hint.tooltip; + + // (1) Inlay Tooltip + let contents: IMarkdownString | undefined; + if (typeof part.item.hint.tooltip === 'string') { + contents = new MarkdownString().appendText(part.item.hint.tooltip); + } else if (part.item.hint.tooltip) { + contents = part.item.hint.tooltip; + } + if (contents) { + executor.emitOne(new MarkdownHover(this, anchor.range, [contents], 0)); } - return [new MarkdownHover(this, anchor.range, [contents], 0)]; - })); + + // (2) Inlay Label Part Tooltip + const iterable = await this._resolveInlayHintLabelPartHover(part, token); + for await (let item of iterable) { + executor.emitOne(item); + } + }); + } + + private async _resolveInlayHintLabelPartHover(part: InlayHintLabelPart, token: CancellationToken): Promise> { + if (typeof part.item.hint.label === 'string') { + return AsyncIterableObject.EMPTY; + } + + const candidate = part.part.action; + + if (!candidate || Command.is(candidate)) { + // LOCATION + return AsyncIterableObject.EMPTY; + } + const { uri, range } = candidate; + const ref = await this._resolverService.createModelReference(uri); + try { + const model = ref.object.textEditorModel; + if (!HoverProviderRegistry.has(model)) { + return AsyncIterableObject.EMPTY; + } + return getHover(model, new Position(range.startLineNumber, range.startColumn), token) + .filter(item => !isEmptyMarkdownString(item.hover.contents)) + .map(item => new MarkdownHover(this, part.item.anchor.range, item.hover.contents, item.ordinal)); + } finally { + ref.dispose(); + } } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b53a6c8b6450d..9c41e1a14bc97 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6844,8 +6844,14 @@ declare namespace monaco.languages { Parameter = 2 } - export interface InlayHint { + export interface InlayHintLabelPart { label: string; + collapsible?: boolean; + action?: Command | Location; + } + + export interface InlayHint { + label: string | InlayHintLabelPart[]; tooltip?: string | IMarkdownString; position: IPosition; kind: InlayHintKind; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 60cdd4766751f..3a448319f3c97 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -23,6 +23,7 @@ import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy' import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { mixin } from 'vs/base/common/objects'; import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; +import { revive } from 'vs/base/common/marshalling'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -557,7 +558,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return; } return { - hints: result.hints, + hints: revive(result.hints), dispose: () => { if (result.cacheId) { this._proxy.$releaseInlayHints(handle, result.cacheId); @@ -579,7 +580,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { ...hint, tooltip: result.tooltip, - label: result.label + label: revive(result.label) }; }; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a10fe9f4fcbd9..c497e643b2e78 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1276,6 +1276,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I WorkspaceEdit: extHostTypes.WorkspaceEdit, // proposed api types InlayHint: extHostTypes.InlayHint, + InlayHintLabelPart: extHostTypes.InlayHintLabelPart, InlayHintKind: extHostTypes.InlayHintKind, RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, ResolvedAuthority: extHostTypes.ResolvedAuthority, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3f8407431ac30..f148381b4c627 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1504,7 +1504,7 @@ export interface ISignatureHelpContextDto { export interface IInlayHintDto { cacheId?: ChainedCacheId; - label: string; + label: string | modes.InlayHintLabelPart[]; tooltip?: string | IMarkdownString; position: IPosition; kind: modes.InlayHintKind; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 9c1e68debf72f..4b6e53b0e6f43 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -332,8 +332,8 @@ const newCommands: ApiCommand[] = [ new ApiCommand( 'vscode.executeInlayHintProvider', '_executeInlayHintProvider', 'Execute inlay hints provider', [ApiCommandArgument.Uri, ApiCommandArgument.Range], - new ApiCommandResult('A promise that resolves to an array of Inlay objects', result => { - return result.map(typeConverters.InlayHint.to); + new ApiCommandResult('A promise that resolves to an array of Inlay objects', (result, args, converter) => { + return result.map(typeConverters.InlayHint.to.bind(undefined, converter)); }) ), // --- notebooks diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 5bcf93a9dbc66..073ab74312949 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import type * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, InlayHintKind, Location } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/languages'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -1173,9 +1173,11 @@ class SignatureHelpAdapter { class InlayHintsAdapter { private _cache = new Cache('InlayHints'); + private readonly _disposables = new Map(); constructor( private readonly _documents: ExtHostDocuments, + private readonly _commands: CommandsConverter, private readonly _provider: vscode.InlayHintsProvider, ) { } @@ -1192,21 +1194,13 @@ class InlayHintsAdapter { // of results as they will leak return undefined; } - if (typeof this._provider.resolveInlayHint !== 'function') { - // no resolve -> no caching - return { hints: hints.map(typeConvert.InlayHint.from) }; - - } else { - // cache links for future resolving - const pid = this._cache.add(hints); - const result: extHostProtocol.IInlayHintsDto = { hints: [], cacheId: pid }; - for (let i = 0; i < hints.length; i++) { - const dto: extHostProtocol.IInlayHintDto = typeConvert.InlayHint.from(hints[i]); - dto.cacheId = [pid, i]; - result.hints.push(dto); - } - return result; + const pid = this._cache.add(hints); + this._disposables.set(pid, new DisposableStore()); + const result: extHostProtocol.IInlayHintsDto = { hints: [], cacheId: pid }; + for (let i = 0; i < hints.length; i++) { + result.hints.push(this._convertInlayHint(hints[i], [pid, i])); } + return result; } async resolveInlayHint(id: extHostProtocol.ChainedCacheId, token: CancellationToken) { @@ -1221,12 +1215,51 @@ class InlayHintsAdapter { if (!hint) { return undefined; } - return typeConvert.InlayHint.from(hint); + if (token.isCancellationRequested) { + return undefined; + } + return this._convertInlayHint(hint, id); } releaseHints(id: number): any { + this._disposables.get(id)?.dispose(); + this._disposables.delete(id); this._cache.delete(id); } + + private _convertInlayHint(hint: vscode.InlayHint, id: extHostProtocol.ChainedCacheId): extHostProtocol.IInlayHintDto { + + const disposables = this._disposables.get(id[0]); + if (!disposables) { + throw Error('DisposableStore is missing...'); + } + + const result: extHostProtocol.IInlayHintDto = { + label: '', // fill-in below + cacheId: id, + tooltip: hint.tooltip && typeConvert.MarkdownString.from(hint.tooltip), + position: typeConvert.Position.from(hint.position), + kind: typeConvert.InlayHintKind.from(hint.kind ?? InlayHintKind.Other), + whitespaceBefore: hint.whitespaceBefore, + whitespaceAfter: hint.whitespaceAfter, + }; + + if (typeof hint.label === 'string') { + result.label = hint.label; + } else { + result.label = hint.label.map(part => { + let r: modes.InlayHintLabelPart = { label: part.label }; + r.collapsible = part.collapsible; + if (Location.isLocation(part.action)) { + r.action = typeConvert.location.from(part.action); + } else if (part.action) { + r.action = this._commands.toInternal(part.action, disposables); + } + return r; + }); + } + return result; + } } class LinkProviderAdapter { @@ -2030,7 +2063,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF registerInlayHintsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlayHintsProvider): vscode.Disposable { const eventHandle = typeof provider.onDidChangeInlayHints === 'function' ? this._nextHandle() : undefined; - const handle = this._addNewAdapter(new InlayHintsAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new InlayHintsAdapter(this._documents, this._commands.converter, provider), extension); this._proxy.$registerInlayHintsProvider(handle, this._transformDocumentSelector(selector), typeof provider.resolveInlayHint === 'function', eventHandle); let result = this._createDisposable(handle); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 072dfa84e35b3..bb20058963014 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1152,20 +1152,9 @@ export namespace SignatureHelp { export namespace InlayHint { - export function from(hint: vscode.InlayHint): modes.InlayHint { - return { - label: hint.text, - tooltip: hint.tooltip && MarkdownString.from(hint.tooltip), - position: Position.from(hint.position), - kind: InlayHintKind.from(hint.kind ?? types.InlayHintKind.Other), - whitespaceBefore: hint.whitespaceBefore, - whitespaceAfter: hint.whitespaceAfter, - }; - } - - export function to(hint: modes.InlayHint): vscode.InlayHint { + export function to(converter: CommandsConverter, hint: modes.InlayHint): vscode.InlayHint { const res = new types.InlayHint( - hint.label, + typeof hint.label === 'string' ? hint.label : hint.label.map(InlayHintLabelPart.to.bind(undefined, converter)), Position.to(hint.position), InlayHintKind.to(hint.kind) ); @@ -1176,6 +1165,20 @@ export namespace InlayHint { } } +export namespace InlayHintLabelPart { + + export function to(converter: CommandsConverter, part: modes.InlayHintLabelPart): types.InlayHintLabelPart { + const result = new types.InlayHintLabelPart(part.label); + result.collapsible = part.collapsible; + if (modes.Command.is(part.action)) { + result.action = converter.fromInternal(part.action); + } else if (part.action) { + result.action = location.to(part.action); + } + return result; + } +} + export namespace InlayHintKind { export function from(kind: vscode.InlayHintKind): modes.InlayHintKind { return kind; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 789b7cc752754..99762f88a3de8 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -873,7 +873,7 @@ export enum DiagnosticSeverity { @es5ClassCompat export class Location { - static isLocation(thing: any): thing is Location { + static isLocation(thing: any): thing is vscode.Location { if (thing instanceof Location) { return true; } @@ -1421,16 +1421,29 @@ export enum InlayHintKind { } @es5ClassCompat -export class InlayHint { - text: string; +export class InlayHintLabelPart { + label: string; + collapsible?: boolean; + action?: vscode.Command | Location; // invokes provider + constructor(label: string) { + this.label = label; + } + toString(): string { + return this.label; + } +} + +@es5ClassCompat +export class InlayHint implements vscode.InlayHint { + label: string | InlayHintLabelPart[]; tooltip?: string | vscode.MarkdownString; position: Position; kind?: vscode.InlayHintKind; whitespaceBefore?: boolean; whitespaceAfter?: boolean; - constructor(text: string, position: Position, kind?: vscode.InlayHintKind) { - this.text = text; + constructor(label: string | InlayHintLabelPart[], position: Position, kind?: vscode.InlayHintKind) { + this.label = label; this.position = position; this.kind = kind; } diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index fc0c761c36e21..91e9cf2ce6865 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -1248,7 +1248,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(value.length, 1); const [first] = value; - assert.strictEqual(first.text, 'Foo'); + assert.strictEqual(first.label, 'Foo'); assert.strictEqual(first.position.line, 0); assert.strictEqual(first.position.character, 1); }); @@ -1273,11 +1273,11 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(value.length, 2); const [first, second] = value; - assert.strictEqual(first.text, 'Foo'); + assert.strictEqual(first.label, 'Foo'); assert.strictEqual(first.position.line, 0); assert.strictEqual(first.position.character, 1); - assert.strictEqual(second.text, 'Bar'); + assert.strictEqual(second.label, 'Bar'); assert.strictEqual(second.position.line, 10); assert.strictEqual(second.position.character, 11); }); @@ -1300,7 +1300,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(value.length, 1); const [first] = value; - assert.strictEqual(first.text, 'Foo'); + assert.strictEqual(first.label, 'Foo'); assert.strictEqual(first.position.line, 0); assert.strictEqual(first.position.character, 1); }); diff --git a/src/vscode-dts/vscode.proposed.inlayHints.d.ts b/src/vscode-dts/vscode.proposed.inlayHints.d.ts index 95f7c964f621b..75630e4901fad 100644 --- a/src/vscode-dts/vscode.proposed.inlayHints.d.ts +++ b/src/vscode-dts/vscode.proposed.inlayHints.d.ts @@ -34,15 +34,22 @@ declare module 'vscode' { Parameter = 2, } + export class InlayHintLabelPart { + label: string; + collapsible?: boolean; + // todo@api better name! + action?: Command | Location; // invokes provider + constructor(label: string); + } + /** * Inlay hint information. */ export class InlayHint { /** - * The text of the hint. + * */ - // todo@API label? - text: string; + label: string | InlayHintLabelPart[]; /** * The tooltip text when you hover over this item. */ @@ -65,7 +72,7 @@ declare module 'vscode' { whitespaceAfter?: boolean; // todo@API make range first argument - constructor(text: string, position: Position, kind?: InlayHintKind); + constructor(label: string | InlayHintLabelPart[], position: Position, kind?: InlayHintKind); } /** From 2a751e5356fcd19338974e8cc6b78ec445915b81 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 12:33:15 -0800 Subject: [PATCH 1151/2210] Report shell integration status in hover --- .../contrib/terminal/browser/terminal.ts | 7 ++++++- .../terminal/browser/terminalTabsList.ts | 21 +++++++++++++++++-- .../browser/xterm/shellIntegrationAddon.ts | 2 ++ .../terminal/browser/xterm/xtermTerminal.ts | 5 +++-- .../contrib/terminal/common/terminal.ts | 1 + 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index a0836949d50ad..5d2cf9ccebaaa 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource, TerminalShellType, IExtensionTerminalProfile, TerminalLocation, ProcessPropertyType, ProcessCapability, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal'; -import { ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalFont, ITerminalBackend, ITerminalProcessExtHostProxy, IRegisterContributedProfileArgs } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalFont, ITerminalBackend, ITerminalProcessExtHostProxy, IRegisterContributedProfileArgs, IShellIntegration } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { IEditableData } from 'vs/workbench/common/views'; @@ -808,6 +808,11 @@ export interface IXtermTerminal { */ readonly commandTracker: ICommandTracker; + /** + * Reports the status of shell integration and fires events relating to it. + */ + readonly shellIntegration: IShellIntegration; + /** * The position of the terminal. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 49f2ea82d13c6..805144e2cf89d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -17,7 +17,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ITerminalBackend, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ProcessCapability, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { Codicon } from 'vs/base/common/codicons'; import { Action } from 'vs/base/common/actions'; import { MarkdownString } from 'vs/base/common/htmlContent'; @@ -304,6 +304,16 @@ class TerminalTabsRenderer implements IListRenderer(); @@ -107,6 +108,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati type = ShellIntegrationInteraction.CommandFinished; break; case ShellIntegrationOscPt.EnableShellIntegration: + this.capabilities.push(ProcessCapability.CommandCognisant); this._onCapabilityEnabled.fire(ProcessCapability.CommandCognisant); return true; default: diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 2268f0f6e45bb..a483087f67d0d 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -13,7 +13,7 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ProcessCapability, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { ICommandTracker, ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ICommandTracker, IShellIntegration, ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ILogService } from 'vs/platform/log/common/log'; @@ -55,14 +55,15 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Always on addons private _commandTrackerAddon: CommandTrackerAddon; + private _shellIntegrationAddon: ShellIntegrationAddon; // Optional addons private _searchAddon?: SearchAddonType; - private _shellIntegrationAddon?: ShellIntegrationAddon; private _unicode11Addon?: Unicode11AddonType; private _webglAddon?: WebglAddonType; get commandTracker(): ICommandTracker { return this._commandTrackerAddon; } + get shellIntegration(): IShellIntegration { return this._shellIntegrationAddon; } private _target: TerminalLocation | undefined; set target(location: TerminalLocation | undefined) { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 38c007a2d9d4c..c2a0403856a21 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -332,6 +332,7 @@ export interface ICommandTracker { } export interface IShellIntegration { + readonly capabilities: readonly ProcessCapability[]; readonly onCapabilityEnabled: Event; readonly onCapabilityDisabled: Event; // TODO: Fire more fine-grained and stronger typed events From 9817557314f4aefe7a71a95e80b1b92aec81f37c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Jan 2022 13:58:54 -0800 Subject: [PATCH 1152/2210] Refactors to start transition from ProcessCapability to term capability Part of #139396 --- src/vs/platform/terminal/common/terminal.ts | 30 +++++++++++++++++-- .../terminal/browser/terminalTabsList.ts | 15 ++++++---- .../browser/xterm/shellIntegrationAddon.ts | 13 ++++---- .../terminal/browser/xterm/xtermTerminal.ts | 6 ++-- .../contrib/terminal/common/terminal.ts | 8 ++--- 5 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 09272651552b7..3b1ae69c9e4f8 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -556,8 +556,34 @@ export interface IProcessReadyEvent { } export const enum ProcessCapability { - CwdDetection = 'cwdDetection', - CommandCognisant = 'commandCognisant' + // TODO: Migrate this to use TerminalCapability.NaiveCwdDetection + CwdDetection = 'cwdDetection' +} + +/** + * Primarily driven by the shell integration feature, a terminal capability is the mechanism for + * progressively enhancing various features that may not be supported in all terminals/shells. + */ +export const enum TerminalCapability { + /** + * The terminal can reliably detect the current working directory as soon as the change happens + * within the buffer. + */ + CwdDetection, + /** + * The terminal can reliably detect the current working directory when requested. + */ + NaiveCwdDetection, + /** + * The terminal can reliably identify prompts, commands and command outputs within the buffer. + */ + CommandDetection, + /** + * The terminal can often identify prompts, commands and command outputs within the buffer. It + * may not be so good at remembering the position of commands that ran in the past. This state + * may be enabled when something goes wrong or when using conpty for example. + */ + PartialCommandDetection } /** diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 805144e2cf89d..81758b630de90 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -17,7 +17,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ITerminalBackend, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ProcessCapability, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { TerminalCapability, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { Codicon } from 'vs/base/common/codicons'; import { Action } from 'vs/base/common/actions'; import { MarkdownString } from 'vs/base/common/htmlContent'; @@ -310,7 +310,7 @@ class TerminalTabsRenderer implements IListRenderer(); + private readonly _onCapabilityDisabled = new Emitter(); readonly onCapabilityDisabled = this._onCapabilityDisabled.event; - private readonly _onCapabilityEnabled = new Emitter(); + private readonly _onCapabilityEnabled = new Emitter(); readonly onCapabilityEnabled = this._onCapabilityEnabled.event; private readonly _onIntegratedShellChange = new Emitter<{ type: string, value: string }>(); readonly onIntegratedShellChange = this._onIntegratedShellChange.event; @@ -108,8 +107,8 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati type = ShellIntegrationInteraction.CommandFinished; break; case ShellIntegrationOscPt.EnableShellIntegration: - this.capabilities.push(ProcessCapability.CommandCognisant); - this._onCapabilityEnabled.fire(ProcessCapability.CommandCognisant); + this.capabilities.push(TerminalCapability.CommandDetection); + this._onCapabilityEnabled.fire(TerminalCapability.CommandDetection); return true; default: return false; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index a483087f67d0d..14ddcc2e42395 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -12,7 +12,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { ProcessCapability, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { TerminalCapability, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ICommandTracker, IShellIntegration, ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -152,13 +152,13 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Hook up co-dependent addon events this._shellIntegrationAddon.onCapabilityEnabled(e => { - if (e === ProcessCapability.CommandCognisant) { + if (e === TerminalCapability.CommandDetection) { this.upgradeCommandTracker(); } }); this._shellIntegrationAddon.onIntegratedShellChange(e => { if (e.type === ShellIntegrationInteraction.CommandFinished) { - // TODO: This shoudl move into the new command tracker + // TODO: This should move into the new command tracker if (this.raw.buffer.active.cursorX >= 2) { this.raw.registerMarker(0); this.commandTracker.clearMarker(); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index c2a0403856a21..edab2e9becdac 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, TerminalCommand, ProcessCapability } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, IProcessProperty, TitleEventSource, ProcessPropertyType, IFixedTerminalDimensions, IExtensionTerminalProfile, ICreateContributedTerminalProfileOptions, IProcessPropertyMap, ITerminalEnvironment, TerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/terminal'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; @@ -332,9 +332,9 @@ export interface ICommandTracker { } export interface IShellIntegration { - readonly capabilities: readonly ProcessCapability[]; - readonly onCapabilityEnabled: Event; - readonly onCapabilityDisabled: Event; + readonly capabilities: readonly TerminalCapability[]; + readonly onCapabilityEnabled: Event; + readonly onCapabilityDisabled: Event; // TODO: Fire more fine-grained and stronger typed events readonly onIntegratedShellChange: Event<{ type: string, value: string }>; } From 96bc3fc5dd18ff7150c21e9fa9248b6c383c61ce Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 7 Jan 2022 14:05:29 -0800 Subject: [PATCH 1153/2210] use @vscode/windows-registry --- package.json | 3 +-- .../native/electron-main/nativeHostMainService.ts | 2 +- yarn.lock | 15 +++++---------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 8dee0ee60a6c8..c0ef9d12dd2e1 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,6 @@ "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", - "@types/vscode-windows-registry": "^1.0.0", "@types/webpack": "^4.41.25", "@types/wicg-file-system-access": "^2020.9.2", "@types/windows-foreground-love": "^0.3.0", @@ -221,7 +220,7 @@ "url": "https://github.com/microsoft/vscode/issues" }, "optionalDependencies": { - "vscode-windows-registry": "1.0.4", + "@vscode/windows-registry": "1.0.5", "windows-foreground-love": "0.4.0", "windows-mutex": "0.4.1", "windows-process-tree": "^0.3.2" diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index a735a66da0b9b..d9097dafa88ee 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -816,7 +816,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return undefined; } - const Registry = await import('vscode-windows-registry'); + const Registry = await import('@vscode/windows-registry'); try { return Registry.GetStringRegKey(hive, path, name); } catch { diff --git a/yarn.lock b/yarn.lock index fffdb40c9d03d..b83c1b7867218 100644 --- a/yarn.lock +++ b/yarn.lock @@ -830,11 +830,6 @@ "@types/expect" "^1.20.4" "@types/node" "*" -"@types/vscode-windows-registry@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/vscode-windows-registry/-/vscode-windows-registry-1.0.0.tgz#333eea7fd5743fa4c99dff13af16de2c08a586d0" - integrity sha512-gyq9tIMbxry5GL2gY7J30E6R3EUx0cAin/k3wfsQez4C5uDWVJmJw142x6KFXtYX7xYQL/IXmm4cRqi4ghg05A== - "@types/webpack-sources@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" @@ -989,6 +984,11 @@ resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== +"@vscode/windows-registry@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.0.5.tgz#a6c463ac123ee7b23f9b90935aea086a97a778dc" + integrity sha512-xEA/L3ki8qMSer3hwdm590G43YCjpMb7evqS5aSPFFAhAYepvVr12/szKFgx1v1aQHOcR2riWg5YqBLajOZoaw== + "@webassemblyjs/ast@1.11.0": version "1.11.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" @@ -10567,11 +10567,6 @@ vscode-windows-ca-certs@^0.3.0: dependencies: node-addon-api "^3.0.2" -vscode-windows-registry@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.4.tgz#9e565a497c84b6b82d200f88930baeff12912079" - integrity sha512-vjYaMzEygZrb8bN6I33XTajpF/gtDOk5CtQPPSaxanXg2kkrerEM9qovY6t6FtBGl3oLq6YHytYdYw4IpXgJdA== - watchpack-chokidar2@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" From 9410697460fdd45cc74bc311466f9f2287998911 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 7 Jan 2022 14:13:37 -0800 Subject: [PATCH 1154/2210] add typings file back --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index c0ef9d12dd2e1..a2556f9f234b1 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", + "@types/vscode-windows-registry": "^1.0.0", "@types/webpack": "^4.41.25", "@types/wicg-file-system-access": "^2020.9.2", "@types/windows-foreground-love": "^0.3.0", diff --git a/yarn.lock b/yarn.lock index b83c1b7867218..a232299c4bda9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -830,6 +830,11 @@ "@types/expect" "^1.20.4" "@types/node" "*" +"@types/vscode-windows-registry@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/vscode-windows-registry/-/vscode-windows-registry-1.0.0.tgz#333eea7fd5743fa4c99dff13af16de2c08a586d0" + integrity sha512-gyq9tIMbxry5GL2gY7J30E6R3EUx0cAin/k3wfsQez4C5uDWVJmJw142x6KFXtYX7xYQL/IXmm4cRqi4ghg05A== + "@types/webpack-sources@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" From 5699e3891a83fa59dc15c3db2b7a16de5303c300 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 7 Jan 2022 14:53:04 -0800 Subject: [PATCH 1155/2210] add typings --- package.json | 1 - src/typings/windows-registry.d.ts | 9 +++++++++ yarn.lock | 5 ----- 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 src/typings/windows-registry.d.ts diff --git a/package.json b/package.json index a2556f9f234b1..c0ef9d12dd2e1 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,6 @@ "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", - "@types/vscode-windows-registry": "^1.0.0", "@types/webpack": "^4.41.25", "@types/wicg-file-system-access": "^2020.9.2", "@types/windows-foreground-love": "^0.3.0", diff --git a/src/typings/windows-registry.d.ts b/src/typings/windows-registry.d.ts new file mode 100644 index 0000000000000..165a20613abbf --- /dev/null +++ b/src/typings/windows-registry.d.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module '@vscode/windows-registry' { + export type HKEY = 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG'; + export function GetStringRegKey(hive: HKEY, path: string, name: string): string | undefined; +} diff --git a/yarn.lock b/yarn.lock index a232299c4bda9..b83c1b7867218 100644 --- a/yarn.lock +++ b/yarn.lock @@ -830,11 +830,6 @@ "@types/expect" "^1.20.4" "@types/node" "*" -"@types/vscode-windows-registry@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/vscode-windows-registry/-/vscode-windows-registry-1.0.0.tgz#333eea7fd5743fa4c99dff13af16de2c08a586d0" - integrity sha512-gyq9tIMbxry5GL2gY7J30E6R3EUx0cAin/k3wfsQez4C5uDWVJmJw142x6KFXtYX7xYQL/IXmm4cRqi4ghg05A== - "@types/webpack-sources@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" From abd132c12405254f9ce13695b05f78fd8a30e539 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 7 Jan 2022 14:53:35 -0800 Subject: [PATCH 1156/2210] experimental flag --- .../browser/contrib/find/findController.ts | 22 ++++- .../contrib}/find/simpleFindReplaceWidget.css | 0 .../contrib}/find/simpleFindReplaceWidget.ts | 34 ++++--- .../notebook/browser/notebookEditorWidget.ts | 94 ++++++++++++++++++- .../browser/view/renderers/webviewPreloads.ts | 20 +++- 5 files changed, 146 insertions(+), 24 deletions(-) rename src/vs/workbench/contrib/{codeEditor/browser => notebook/browser/contrib}/find/simpleFindReplaceWidget.css (100%) rename src/vs/workbench/contrib/{codeEditor/browser => notebook/browser/contrib}/find/simpleFindReplaceWidget.ts (96%) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index df2f8c4832c67..d5bbba808a200 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -13,7 +13,6 @@ import { Range } from 'vs/editor/common/core/range'; import { MATCHES_LIMIT } from 'vs/editor/contrib/find/findModel'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import * as DOM from 'vs/base/browser/dom'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; @@ -30,6 +29,7 @@ import { NLS_MATCHES_LOCATION, NLS_NO_RESULTS } from 'vs/editor/contrib/find/fin import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { FindMatch } from 'vs/editor/common/model'; +import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget'; const FIND_HIDE_TRANSITION = 'find-hide-transition'; const FIND_SHOW_TRANSITION = 'find-show-transition'; @@ -43,16 +43,17 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote private _hideTimeout: number | null = null; private _previousFocusElement?: HTMLElement; private _findModel: FindModel; + private _styleElement!: HTMLStyleElement; constructor( private readonly _notebookEditor: INotebookEditor, @IContextViewService contextViewService: IContextViewService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService ) { - super(contextViewService, contextKeyService, themeService, new FindReplaceState(), true); + super(contextViewService, contextKeyService, themeService, configurationService, new FindReplaceState(), true); this._findModel = new FindModel(this._notebookEditor, this._state, this._configurationService); DOM.append(this._notebookEditor.getDomNode(), this.getDomNode()); @@ -79,6 +80,21 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this._register(DOM.addDisposableListener(this.getDomNode(), DOM.EventType.FOCUS, e => { this._previousFocusElement = e.relatedTarget instanceof HTMLElement ? e.relatedTarget : undefined; }, true)); + + this._createLayoutStyles(); + } + + private _createLayoutStyles(): void { + this._styleElement = DOM.createStyleSheet(this.getDomNode()); + const experimental = this._configurationService.getValue('notebook.find.experimental'); + const styleSheets: string[] = []; + if (experimental) { + styleSheets.push('.monaco-workbench .simple-fr-find-part-wrapper { width: 341px; }'); + } else { + styleSheets.push('.monaco-workbench .simple-fr-find-part-wrapper { width: 318px; }'); + } + + this._styleElement.textContent = styleSheets.join('\n'); } private _onFindInputKeyDown(e: IKeyboardEvent): void { diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css b/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.css similarity index 100% rename from src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css rename to src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.css diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts similarity index 96% rename from src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts rename to src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts index 89d141597726a..ae3ef88186d6e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts @@ -25,6 +25,7 @@ import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platf import { parseReplaceString, ReplacePattern } from 'vs/editor/contrib/find/replacePattern'; import { Codicon } from 'vs/base/common/codicons'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -48,7 +49,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { private readonly _findInputFocusTracker: dom.IFocusTracker; private readonly _updateHistoryDelayer: Delayer; protected readonly _matchesCount!: HTMLElement; - protected readonly filterBtn: Checkbox; + protected readonly filterBtn?: Checkbox; private readonly prevBtn: SimpleButton; private readonly nextBtn: SimpleButton; @@ -71,6 +72,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { @IContextViewService private readonly _contextViewService: IContextViewService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, + @IConfigurationService protected readonly _configurationService: IConfigurationService, protected readonly _state: FindReplaceState = new FindReplaceState(), showOptionButtons?: boolean ) { @@ -157,17 +159,6 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._matchesCount.className = 'matchesCount'; this._updateMatchesCount(); - // Toggle filter button - this.filterBtn = this._register(new Checkbox({ - title: NLS_FILTER_BTN_LABEL, - icon: findFilterButton, - isChecked: false - })); - - this._register(this.filterBtn.onChange(() => { - this._state.change({ filters: this.filterBtn.checked }, true); - })); - this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, icon: findPreviousMatchIcon, @@ -194,7 +185,22 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._innerFindDomNode.appendChild(this._findInput.domNode); this._innerFindDomNode.appendChild(this._matchesCount); - this._innerFindDomNode.appendChild(this.filterBtn.domNode); + + + // Toggle filter button + const experimental = this._configurationService.getValue('notebook.find.experimental'); + if (experimental) { + this.filterBtn = this._register(new Checkbox({ + title: NLS_FILTER_BTN_LABEL, + icon: findFilterButton, + isChecked: false + })); + + this._register(this.filterBtn.onChange(() => { + this._state.change({ filters: this.filterBtn!.checked }, true); + })); + this._innerFindDomNode.appendChild(this.filterBtn.domNode); + } this._innerFindDomNode.appendChild(this.prevBtn.domNode); this._innerFindDomNode.appendChild(this.nextBtn.domNode); this._innerFindDomNode.appendChild(closeBtn.domNode); @@ -335,7 +341,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), }; this._replaceInput.style(replaceStyles); - this.filterBtn.style(inputStyles); + this.filterBtn?.style(inputStyles); } private _onStateChanged(e: FindReplaceStateChangedEvent): void { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0fc185a1275e8..996aa1a54111b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -55,7 +55,7 @@ import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/v import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, INotebookViewCellsUpdateEvent, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, INotebookSearchOptions, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BUILTIN_RENDERER_ID, CellKind, INotebookSearchOptions, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; import { IWebview } from 'vs/workbench/contrib/webview/browser/webview'; @@ -72,6 +72,7 @@ import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/no import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger'; import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; const $ = DOM.$; @@ -295,6 +296,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onMouseDown: Event = this._onMouseDown.event; private readonly _onDidReceiveMessage = this._register(new Emitter()); readonly onDidReceiveMessage: Event = this._onDidReceiveMessage.event; + private readonly _onDidRenderOutput = this._register(new Emitter()); + private readonly onDidRenderOutput = this._onDidRenderOutput.event; + //#endregion private _overlayContainer!: HTMLElement; @@ -398,6 +402,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD @INotebookRendererMessagingService private readonly notebookRendererMessaging: INotebookRendererMessagingService, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, @INotebookKernelService private readonly notebookKernelService: INotebookKernelService, + @INotebookService private readonly _notebookService: INotebookService, @IConfigurationService private readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, @ILayoutService private readonly layoutService: ILayoutService, @@ -2261,7 +2266,86 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD //#endregion //#region Find - async find(query: string, options: INotebookSearchOptions, token: CancellationToken): Promise { + + private async _renderCell(viewCell: CodeCellViewModel) { + if (viewCell.isOutputCollapsed) { + return; + } + + const outputs = viewCell.outputsViewModels; + for (let output of outputs) { + const [mimeTypes, pick] = output.resolveMimeTypes(this.textModel!, undefined); + if (!mimeTypes.find(mimeType => mimeType.isTrusted) || mimeTypes.length === 0) { + continue; + } + + const pickedMimeTypeRenderer = mimeTypes[pick]; + + if (!pickedMimeTypeRenderer) { + return; + } + + if (pickedMimeTypeRenderer.rendererId === BUILTIN_RENDERER_ID) { + const renderer = this.getOutputRenderer().getContribution(pickedMimeTypeRenderer.mimeType); + if (renderer?.getType() === RenderOutputType.Html) { + const renderResult = renderer.render(output, output.model.outputs.filter(op => op.mime === pickedMimeTypeRenderer.mimeType)[0], DOM.$(''), this.textModel!.uri) as IInsetRenderOutput; + if (!this._webview?.insetMapping.has(renderResult.source)) { + const p = new Promise(resolve => { + this.onDidRenderOutput(e => { + if (e.model === renderResult.source.model) { + resolve(); + } + }); + }); + this.createOutput(viewCell, renderResult, 0); + await p; + return; + } + } + return; + } + const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); + + if (!renderer) { + return; + } + + const result: IInsetRenderOutput = { type: RenderOutputType.Extension, renderer, source: output, mimeType: pickedMimeTypeRenderer.mimeType }; + if (!this._webview?.insetMapping.has(result.source)) { + const p = new Promise(resolve => { + this.onDidRenderOutput(e => { + if (e.model === result.source.model) { + resolve(); + } + }); + }); + this.createOutput(viewCell, result, 0); + await p; + + } + + return; + } + + } + private async _warmupAll() { + if (!this.hasModel()) { + return; + } + + const renderPromises = []; + for (let i = 0; i < this.getLength(); i++) { + const cell = this.cellAt(i); + + if (cell?.cellKind === CellKind.Code) { + renderPromises.push(this._renderCell((cell as CodeCellViewModel))); + } + } + + return Promise.all(renderPromises); + } + + async find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup: boolean = false): Promise { if (!this._notebookViewModel) { return []; } @@ -2279,6 +2363,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }); if (this._webview) { + // request all outputs to be rendered + await this._warmupAll(); const webviewMatches = await this._webview.find(query); // attach webview matches to model find matches webviewMatches.forEach(match => { @@ -2634,6 +2720,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._debug('update cell output', cell.handle, outputHeight); cell.updateOutputHeight(outputIndex, outputHeight, source); this.layoutNotebookCell(cell, cell.layoutInfo.totalHeight); + + if (isInit) { + this._onDidRenderOutput.fire(output); + } } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 07cf35128c44d..329687430a300 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -226,11 +226,21 @@ async function webviewPreloads(ctx: PreloadContext) { this.updateImmediately(); }, 0); } - this.pending.set(id, { - id, - height, - ...options, - }); + const update = this.pending.get(id); + if (update && update.isOutput) { + this.pending.set(id, { + id, + height, + init: update.init, + isOutput: update.isOutput, + }); + } else { + this.pending.set(id, { + id, + height, + ...options, + }); + } } updateImmediately() { From f9d19ea2ae9ad3893b818c870fd93d1d05a9f40b Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 7 Jan 2022 15:01:53 -0800 Subject: [PATCH 1157/2210] Update label --- .../notebook/browser/contrib/find/simpleFindReplaceWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts index ae3ef88186d6e..4029985c6f1b8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts @@ -30,7 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous Match"); -const NLS_FILTER_BTN_LABEL = nls.localize('label.findFilterButton', "Filter"); +const NLS_FILTER_BTN_LABEL = nls.localize('label.findFilterButton', "Search in View"); const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next Match"); const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace"); From f4d8fe9c93bb9e7c98b084aff73042cb648fa89e Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 7 Jan 2022 15:05:35 -0800 Subject: [PATCH 1158/2210] update widget ref --- .../contrib/notebook/browser/contrib/find/findController.ts | 2 +- ...impleFindReplaceWidget.css => notebookFindReplaceWidget.css} | 0 ...{simpleFindReplaceWidget.ts => notebookFindReplaceWidget.ts} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/vs/workbench/contrib/notebook/browser/contrib/find/{simpleFindReplaceWidget.css => notebookFindReplaceWidget.css} (100%) rename src/vs/workbench/contrib/notebook/browser/contrib/find/{simpleFindReplaceWidget.ts => notebookFindReplaceWidget.ts} (99%) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index d5bbba808a200..3929f99a29934 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -29,7 +29,7 @@ import { NLS_MATCHES_LOCATION, NLS_NO_RESULTS } from 'vs/editor/contrib/find/fin import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { FindMatch } from 'vs/editor/common/model'; -import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget'; +import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget'; const FIND_HIDE_TRANSITION = 'find-hide-transition'; const FIND_SHOW_TRANSITION = 'find-show-transition'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.css b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css similarity index 100% rename from src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.css rename to src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts similarity index 99% rename from src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts rename to src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 4029985c6f1b8..f0032bf31e7ad 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/simpleFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -11,7 +11,7 @@ import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Widget } from 'vs/base/browser/ui/widget'; import { Delayer } from 'vs/base/common/async'; import { KeyCode } from 'vs/base/common/keyCodes'; -import 'vs/css!./simpleFindReplaceWidget'; +import 'vs/css!./notebookFindReplaceWidget'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; import { findNextMatchIcon, findPreviousMatchIcon, findReplaceAllIcon, findReplaceIcon, SimpleButton } from 'vs/editor/contrib/find/findWidget'; import * as nls from 'vs/nls'; From 2766a74d2ac9e52fe38115abae3756d3db8d081e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sat, 8 Jan 2022 00:36:52 +0100 Subject: [PATCH 1159/2210] - move migration into a util function - extract extension storage methods to a separate service --- .../sharedProcess/sharedProcessMain.ts | 4 +- .../common/extensionStorage.ts | 148 ++++++++++++++++++ .../common/extensionsStorageSync.ts | 106 ------------- .../userDataSync/common/extensionsSync.ts | 44 ++---- .../test/common/userDataSyncClient.ts | 4 +- .../api/browser/mainThreadStorage.ts | 106 ++----------- .../workbench/api/common/extHost.protocol.ts | 8 +- src/vs/workbench/api/common/extHostMemento.ts | 2 +- src/vs/workbench/api/common/extHostStorage.ts | 6 +- .../common/extensionStorageMigration.ts | 77 +++++++++ .../services/userData/browser/userDataInit.ts | 13 +- src/vs/workbench/workbench.common.main.ts | 4 +- 12 files changed, 277 insertions(+), 245 deletions(-) create mode 100644 src/vs/platform/extensionManagement/common/extensionStorage.ts delete mode 100644 src/vs/platform/userDataSync/common/extensionsStorageSync.ts create mode 100644 src/vs/workbench/services/extensions/common/extensionStorageMigration.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 5a8ac34d5d683..a4147b5738299 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -69,7 +69,7 @@ import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/custo import { LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; -import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration as registerUserDataSyncConfiguration } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; @@ -327,7 +327,7 @@ class SharedProcessMain extends Disposable { services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(this.server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService)); services.set(IIgnoredExtensionsManagementService, new SyncDescriptor(IgnoredExtensionsManagementService)); - services.set(IExtensionsStorageSyncService, new SyncDescriptor(ExtensionsStorageSyncService)); + services.set(IExtensionStorageService, new SyncDescriptor(ExtensionStorageService)); services.set(IUserDataSyncStoreManagementService, new SyncDescriptor(UserDataSyncStoreManagementService)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService)); diff --git a/src/vs/platform/extensionManagement/common/extensionStorage.ts b/src/vs/platform/extensionManagement/common/extensionStorage.ts new file mode 100644 index 0000000000000..c2ad01f139017 --- /dev/null +++ b/src/vs/platform/extensionManagement/common/extensionStorage.ts @@ -0,0 +1,148 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { adoptToGalleryExtensionId, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { distinct } from 'vs/base/common/arrays'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IExtension } from 'vs/platform/extensions/common/extensions'; +import { isString } from 'vs/base/common/types'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; + +export interface IExtensionIdWithVersion { + id: string; + version: string; +} + +export const IExtensionStorageService = createDecorator('IExtensionStorageService'); + +export interface IExtensionStorageService { + readonly _serviceBrand: undefined; + + getExtensionState(extension: IExtension | IGalleryExtension | string, global: boolean): IStringDictionary | undefined; + setExtensionState(extension: IExtension | IGalleryExtension | string, state: IStringDictionary | undefined, global: boolean): void; + + readonly onDidChangeExtensionStorageToSync: Event; + setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void; + getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined; +} + +const EXTENSION_KEYS_ID_VERSION_REGEX = /^extensionKeys\/([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/; + +export class ExtensionStorageService extends Disposable implements IExtensionStorageService { + + readonly _serviceBrand: undefined; + + private static toKey(extension: IExtensionIdWithVersion): string { + return `extensionKeys/${adoptToGalleryExtensionId(extension.id)}@${extension.version}`; + } + + private static fromKey(key: string): IExtensionIdWithVersion | undefined { + const matches = EXTENSION_KEYS_ID_VERSION_REGEX.exec(key); + if (matches && matches[1]) { + return { id: matches[1], version: matches[2] }; + } + return undefined; + } + + private readonly _onDidChangeExtensionStorageToSync = this._register(new Emitter()); + readonly onDidChangeExtensionStorageToSync = this._onDidChangeExtensionStorageToSync.event; + + private readonly extensionsWithKeysForSync = new Set(); + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IProductService private readonly productService: IProductService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.initialize(); + this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorageValue(e))); + } + + private initialize(): void { + const keys = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE); + for (const key of keys) { + const extensionIdWithVersion = ExtensionStorageService.fromKey(key); + if (extensionIdWithVersion) { + this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase()); + } + } + } + + private onDidChangeStorageValue(e: IStorageValueChangeEvent): void { + if (e.scope !== StorageScope.GLOBAL) { + return; + } + + // State of extension with keys for sync has changed + if (this.extensionsWithKeysForSync.has(e.key.toLowerCase())) { + this._onDidChangeExtensionStorageToSync.fire(); + return; + } + + // Keys for sync of an extension has changed + const extensionIdWithVersion = ExtensionStorageService.fromKey(e.key); + if (extensionIdWithVersion) { + this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase()); + this._onDidChangeExtensionStorageToSync.fire(); + return; + } + } + + private getExtensionId(extension: IExtension | IGalleryExtension | string): string { + if (isString(extension)) { + return extension; + } + const publisher = (extension as IExtension).manifest ? (extension as IExtension).manifest.publisher : (extension as IGalleryExtension).publisher; + const name = (extension as IExtension).manifest ? (extension as IExtension).manifest.name : (extension as IGalleryExtension).name; + return getExtensionId(publisher, name); + } + + getExtensionState(extension: IExtension | IGalleryExtension | string, global: boolean): IStringDictionary | undefined { + const extensionId = this.getExtensionId(extension); + const jsonValue = this.storageService.get(extensionId, global ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + if (jsonValue) { + try { + return JSON.parse(jsonValue); + } catch (error) { + // Do not fail this call but log it for diagnostics + // https://github.com/microsoft/vscode/issues/132777 + this.logService.error(`[mainThreadStorage] unexpected error parsing storage contents (extensionId: ${extensionId}, global: ${global}): ${error}`); + } + } + + return undefined; + } + + setExtensionState(extension: IExtension | IGalleryExtension | string, state: IStringDictionary | undefined, global: boolean): void { + const extensionId = this.getExtensionId(extension); + if (state === undefined) { + this.storageService.remove(extensionId, global ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + } else { + this.storageService.store(extensionId, JSON.stringify(state), global ? StorageScope.GLOBAL : StorageScope.WORKSPACE, StorageTarget.MACHINE /* Extension state is synced separately through extensions */); + } + } + + setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void { + this.storageService.store(ExtensionStorageService.toKey(extensionIdWithVersion), JSON.stringify(keys), StorageScope.GLOBAL, StorageTarget.MACHINE); + } + + getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined { + const extensionKeysForSyncFromProduct = this.productService.extensionSyncedKeys?.[extensionIdWithVersion.id.toLowerCase()]; + const extensionKeysForSyncFromStorageValue = this.storageService.get(ExtensionStorageService.toKey(extensionIdWithVersion), StorageScope.GLOBAL); + const extensionKeysForSyncFromStorage = extensionKeysForSyncFromStorageValue ? JSON.parse(extensionKeysForSyncFromStorageValue) : undefined; + + return extensionKeysForSyncFromStorage && extensionKeysForSyncFromProduct + ? distinct([...extensionKeysForSyncFromStorage, ...extensionKeysForSyncFromProduct]) + : (extensionKeysForSyncFromStorage || extensionKeysForSyncFromProduct); + } + +} diff --git a/src/vs/platform/userDataSync/common/extensionsStorageSync.ts b/src/vs/platform/userDataSync/common/extensionsStorageSync.ts deleted file mode 100644 index caa44b0923798..0000000000000 --- a/src/vs/platform/userDataSync/common/extensionsStorageSync.ts +++ /dev/null @@ -1,106 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { distinct } from 'vs/base/common/arrays'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; - -export interface IExtensionIdWithVersion { - id: string; - version: string; -} - -export const IExtensionsStorageSyncService = createDecorator('IExtensionsStorageSyncService'); - -export interface IExtensionsStorageSyncService { - - _serviceBrand: any; - - readonly onDidChangeExtensionsStorage: Event; - setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void; - getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined; - -} - -const EXTENSION_KEYS_ID_VERSION_REGEX = /^extensionKeys\/([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/; - -export class ExtensionsStorageSyncService extends Disposable implements IExtensionsStorageSyncService { - - declare readonly _serviceBrand: undefined; - - private static toKey(extension: IExtensionIdWithVersion): string { - return `extensionKeys/${adoptToGalleryExtensionId(extension.id)}@${extension.version}`; - } - - private static fromKey(key: string): IExtensionIdWithVersion | undefined { - const matches = EXTENSION_KEYS_ID_VERSION_REGEX.exec(key); - if (matches && matches[1]) { - return { id: matches[1], version: matches[2] }; - } - return undefined; - } - - private readonly _onDidChangeExtensionsStorage = this._register(new Emitter()); - readonly onDidChangeExtensionsStorage = this._onDidChangeExtensionsStorage.event; - - private readonly extensionsWithKeysForSync = new Set(); - - constructor( - @IStorageService private readonly storageService: IStorageService, - @IProductService private readonly productService: IProductService, - ) { - super(); - this.initialize(); - this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorageValue(e))); - } - - private initialize(): void { - const keys = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE); - for (const key of keys) { - const extensionIdWithVersion = ExtensionsStorageSyncService.fromKey(key); - if (extensionIdWithVersion) { - this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase()); - } - } - } - - private onDidChangeStorageValue(e: IStorageValueChangeEvent): void { - if (e.scope !== StorageScope.GLOBAL) { - return; - } - - // State of extension with keys for sync has changed - if (this.extensionsWithKeysForSync.has(e.key.toLowerCase())) { - this._onDidChangeExtensionsStorage.fire(); - return; - } - - // Keys for sync of an extension has changed - const extensionIdWithVersion = ExtensionsStorageSyncService.fromKey(e.key); - if (extensionIdWithVersion) { - this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase()); - this._onDidChangeExtensionsStorage.fire(); - return; - } - } - - setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void { - this.storageService.store(ExtensionsStorageSyncService.toKey(extensionIdWithVersion), JSON.stringify(keys), StorageScope.GLOBAL, StorageTarget.MACHINE); - } - - getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined { - const extensionKeysForSyncFromProduct = this.productService.extensionSyncedKeys?.[extensionIdWithVersion.id.toLowerCase()]; - const extensionKeysForSyncFromStorageValue = this.storageService.get(ExtensionsStorageSyncService.toKey(extensionIdWithVersion), StorageScope.GLOBAL); - const extensionKeysForSyncFromStorage = extensionKeysForSyncFromStorageValue ? JSON.parse(extensionKeysForSyncFromStorageValue) : undefined; - - return extensionKeysForSyncFromStorage && extensionKeysForSyncFromProduct - ? distinct([...extensionKeysForSyncFromStorage, ...extensionKeysForSyncFromProduct]) - : (extensionKeysForSyncFromStorage || extensionKeysForSyncFromProduct); - } -} diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index c0afb917a1b9c..59a5944dbad85 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -13,17 +13,17 @@ import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, ExtensionManagementError, ExtensionManagementErrorCode, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { IMergeResult as IExtensionMergeResult, merge } from 'vs/platform/userDataSync/common/extensionsMerge'; -import { IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { Change, IRemoteUserData, ISyncData, ISyncExtension, ISyncExtensionWithVersion, ISyncResourceHandle, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; @@ -67,15 +67,6 @@ async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagemen return extensions; } -export function getExtensionStorageState(publisher: string, name: string, storageService: IStorageService): IStringDictionary { - const extensionStorageValue = storageService.get(getExtensionId(publisher, name) /* use the same id used in extension host */, StorageScope.GLOBAL) || '{}'; - return JSON.parse(extensionStorageValue); -} - -export function storeExtensionStorageState(publisher: string, name: string, extensionState: IStringDictionary, storageService: IStorageService): void { - storageService.store(getExtensionId(publisher, name) /* use the same id used in extension host */, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE); -} - export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/extensions.json` }); @@ -96,7 +87,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse constructor( @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, - @IStorageService private readonly storageService: IStorageService, + @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @@ -107,7 +98,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, - @IExtensionsStorageSyncService private readonly extensionsStorageSyncService: IExtensionsStorageSyncService, + @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { super(SyncResource.Extensions, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); @@ -117,7 +108,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse Event.filter(this.extensionManagementService.onDidInstallExtensions, (e => e.some(({ local }) => !!local))), Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)), this.extensionEnablementService.onDidChangeEnablement, - this.extensionsStorageSyncService.onDidChangeExtensionsStorage), + this.extensionStorageService.onDidChangeExtensionStorageToSync), () => undefined, 500)(() => this.triggerLocalChange())); } @@ -368,7 +359,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // Builtin Extension Sync: Enablement & State if (installedExtension && installedExtension.isBuiltin) { if (e.state && installedExtension.manifest.version === e.version) { - this.updateExtensionState(e.state, installedExtension.manifest.publisher, installedExtension.manifest.name, installedExtension.manifest.version); + this.updateExtensionState(e.state, installedExtension, installedExtension.manifest.version); } const isDisabled = this.extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); if (isDisabled !== !!e.disabled) { @@ -397,9 +388,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse (installedExtension ? installedExtension.manifest.version === e.version /* Installed and has same version */ : !!extension /* Installable */) ) { - const publisher = installedExtension ? installedExtension.manifest.publisher : extension!.publisher; - const name = installedExtension ? installedExtension.manifest.name : extension!.name; - this.updateExtensionState(e.state, publisher, name, installedExtension?.manifest.version); + this.updateExtensionState(e.state, installedExtension || extension, installedExtension?.manifest.version); } if (extension) { @@ -460,15 +449,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return newSkippedExtensions; } - private updateExtensionState(state: IStringDictionary, publisher: string, name: string, version: string | undefined): void { - const extensionState = getExtensionStorageState(publisher, name, this.storageService); - const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id: getGalleryExtensionId(publisher, name), version }) : undefined; + private updateExtensionState(state: IStringDictionary, extension: ILocalExtension | IGalleryExtension, version: string | undefined): void { + const extensionState = this.extensionStorageService.getExtensionState(extension, true) || {}; + const keys = version ? this.extensionStorageService.getKeysForSync({ id: extension.identifier.id, version }) : undefined; if (keys) { keys.forEach(key => { extensionState[key] = state[key]; }); } else { Object.keys(state).forEach(key => extensionState[key] = state[key]); } - storeExtensionStorageState(publisher, name, extensionState, this.storageService); + this.extensionStorageService.setExtensionState(extension, extensionState, true); } private parseExtensions(syncData: ISyncData): ISyncExtension[] { @@ -478,7 +467,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private getLocalExtensions(installedExtensions: ILocalExtension[]): ISyncExtensionWithVersion[] { const disabledExtensions = this.extensionEnablementService.getDisabledExtensions(); return installedExtensions - .map(({ identifier, isBuiltin, manifest, preRelease }) => { + .map(extension => { + const { identifier, isBuiltin, manifest, preRelease } = extension; const syncExntesion: ISyncExtensionWithVersion = { identifier, preRelease, version: manifest.version }; if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { syncExntesion.disabled = true; @@ -487,9 +477,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse syncExntesion.installed = true; } try { - const keys = this.extensionsStorageSyncService.getKeysForSync({ id: identifier.id, version: manifest.version }); + const keys = this.extensionStorageService.getKeysForSync({ id: identifier.id, version: manifest.version }); if (keys) { - const extensionStorageState = getExtensionStorageState(manifest.publisher, manifest.name, this.storageService); + const extensionStorageState = this.extensionStorageService.getExtensionState(extension, true) || {}; syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary, key) => { if (keys.includes(key)) { state[key] = extensionStorageState[key]; diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index df284282c0e14..02729fc3378ea 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -32,7 +32,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { ALL_SYNC_RESOURCES, getDefaultIgnoredSettings, IUserData, IUserDataManifest, IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration, ServerResource, SyncResource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; @@ -113,7 +113,7 @@ export class UserDataSyncClient extends Disposable { onDidUninstallExtension: new Emitter().event, }); this.instantiationService.stub(IGlobalExtensionEnablementService, this._register(this.instantiationService.createInstance(GlobalExtensionEnablementService))); - this.instantiationService.stub(IExtensionsStorageSyncService, this._register(this.instantiationService.createInstance(ExtensionsStorageSyncService))); + this.instantiationService.stub(IExtensionStorageService, this._register(this.instantiationService.createInstance(ExtensionStorageService))); this.instantiationService.stub(IIgnoredExtensionsManagementService, this.instantiationService.createInstance(IgnoredExtensionsManagementService)); this.instantiationService.stub(IExtensionGalleryService, >{ isEnabled() { return true; }, diff --git a/src/vs/workbench/api/browser/mainThreadStorage.ts b/src/vs/workbench/api/browser/mainThreadStorage.ts index 480a68264180c..bd6ff064ac838 100644 --- a/src/vs/workbench/api/browser/mainThreadStorage.ts +++ b/src/vs/workbench/api/browser/mainThreadStorage.ts @@ -3,18 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { MainThreadStorageShape, MainContext, IExtHostContext, ExtHostStorageShape, ExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionIdWithVersion, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; -import { ILogService } from 'vs/platform/log/common/log'; -import { FileSystemProviderError, FileSystemProviderErrorCode, IFileService } from 'vs/platform/files/common/files'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { isWeb } from 'vs/base/common/platform'; -import { getErrorMessage } from 'vs/base/common/errors'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IExtensionIdWithVersion, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; +import { migrateExtensionStorage } from 'vs/workbench/services/extensions/common/extensionStorageMigration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @extHostNamedCustomer(MainContext.MainThreadStorage) export class MainThreadStorage implements MainThreadStorageShape { @@ -25,20 +21,16 @@ export class MainThreadStorage implements MainThreadStorageShape { constructor( extHostContext: IExtHostContext, + @IExtensionStorageService private readonly _extensionStorageService: IExtensionStorageService, @IStorageService private readonly _storageService: IStorageService, - @IExtensionsStorageSyncService private readonly _extensionsStorageSyncService: IExtensionsStorageSyncService, - @IFileService private readonly _fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @ILogService private readonly _logService: ILogService + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostStorage); this._storageListener = this._storageService.onDidChangeValue(e => { const shared = e.scope === StorageScope.GLOBAL; if (shared && this._sharedStorageKeysToWatch.has(e.key)) { - this._proxy.$acceptValue(shared, e.key, this._getValue(shared, e.key)); + this._proxy.$acceptValue(shared, e.key, this._extensionStorageService.getExtensionState(e.key, shared)); } }); } @@ -47,91 +39,21 @@ export class MainThreadStorage implements MainThreadStorageShape { this._storageListener.dispose(); } - async $getValue(shared: boolean, key: string): Promise { - if (isWeb && key !== key.toLowerCase()) { - await this._migrateExtensionStorage(key.toLowerCase(), key, `extension.storage.migrateFromLowerCaseKey.${key.toLowerCase()}`); + async $initializeExtensionStorage(shared: boolean, extensionId: string): Promise { + if (isWeb && extensionId !== extensionId.toLowerCase()) { + await migrateExtensionStorage(extensionId.toLowerCase(), extensionId, `extension.storage.migrateFromLowerCaseKey.${extensionId.toLowerCase()}`, this._instantiationService); } if (shared) { - this._sharedStorageKeysToWatch.set(key, true); + this._sharedStorageKeysToWatch.set(extensionId, true); } - return this._getValue(shared, key); - } - - private _getValue(shared: boolean, key: string): T | undefined { - const jsonValue = this._storageService.get(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); - if (jsonValue) { - try { - return JSON.parse(jsonValue); - } catch (error) { - // Do not fail this call but log it for diagnostics - // https://github.com/microsoft/vscode/issues/132777 - this._logService.error(`[mainThreadStorage] unexpected error parsing storage contents (key: ${key}, shared: ${shared}): ${error}`); - } - } - - return undefined; + return this._extensionStorageService.getExtensionState(extensionId, shared); } async $setValue(shared: boolean, key: string, value: object): Promise { - this._storageService.store(key, JSON.stringify(value), shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE, StorageTarget.MACHINE /* Extension state is synced separately through extensions */); - } - - private _remove(shared: boolean, key: string): void { - this._storageService.remove(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + this._extensionStorageService.setExtensionState(key, value, shared); } $registerExtensionStorageKeysToSync(extension: IExtensionIdWithVersion, keys: string[]): void { - this._extensionsStorageSyncService.setKeysForSync(extension, keys); - } - - private async _migrateExtensionStorage(from: string, to: string, storageMigratedKey: string): Promise { - if (from === to) { - return; - } - - const extUri = this.uriIdentityService.extUri; - // Migrate Global Storage - if (!this._storageService.getBoolean(storageMigratedKey, StorageScope.GLOBAL, false)) { - const value = this._getValue(true, from); - if (value) { - this.$setValue(true, to, value); - this._remove(true, from); - } - - const fromPath = extUri.joinPath(this.environmentService.globalStorageHome, from); - const toPath = extUri.joinPath(this.environmentService.globalStorageHome, to.toLowerCase() /* Extension id is lower cased for global storage */); - if (!extUri.isEqual(fromPath, toPath)) { - try { - await this._fileService.move(fromPath, toPath, true); - } catch (error) { - if ((error).code !== FileSystemProviderErrorCode.FileNotFound) { - this._logService.info(`Error while migrating global storage from '${from}' to '${to}'`, getErrorMessage(error)); - } - } - } - - this._storageService.store(storageMigratedKey, true, StorageScope.GLOBAL, StorageTarget.MACHINE); - } - - // Migrate Workspace Storage - if (!this._storageService.getBoolean(storageMigratedKey, StorageScope.WORKSPACE, false)) { - const value = this._getValue(false, from); - if (value) { - this.$setValue(false, to, value); - this._remove(false, from); - } - - const fromPath = extUri.joinPath(this.environmentService.workspaceStorageHome, this.workspaceContextService.getWorkspace().id, from); - const toPath = extUri.joinPath(this.environmentService.workspaceStorageHome, this.workspaceContextService.getWorkspace().id, to); - try { - await this._fileService.move(fromPath, toPath, true); - } catch (error) { - if ((error).code !== FileSystemProviderErrorCode.FileNotFound) { - this._logService.info(`Error while migrating workspace storage from '${from}' to '${to}'`, getErrorMessage(error)); - } - } - - this._storageService.store(storageMigratedKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); - } + this._extensionStorageService.setKeysForSync(extension, keys); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index db0307e22df83..f0f13d0c7d5e7 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -41,7 +41,7 @@ import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/pla import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { ICreateContributedTerminalProfileOptions, IProcessProperty, IShellLaunchConfigDto, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { ThemeColor, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { IExtensionIdWithVersion } from 'vs/platform/extensionManagement/common/extensionStorage'; import { WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; @@ -610,8 +610,8 @@ export interface MainThreadStatusBarShape extends IDisposable { } export interface MainThreadStorageShape extends IDisposable { - $getValue(shared: boolean, key: string): Promise; - $setValue(shared: boolean, key: string, value: object): Promise; + $initializeExtensionStorage(shared: boolean, extensionId: string): Promise; + $setValue(shared: boolean, extensionId: string, value: object): Promise; $registerExtensionStorageKeysToSync(extension: IExtensionIdWithVersion, keys: string[]): void; } @@ -2101,7 +2101,7 @@ export interface ExtHostInteractiveShape { } export interface ExtHostStorageShape { - $acceptValue(shared: boolean, key: string, value: object | undefined): void; + $acceptValue(shared: boolean, extensionId: string, value: object | undefined): void; } export interface ExtHostThemingShape { diff --git a/src/vs/workbench/api/common/extHostMemento.ts b/src/vs/workbench/api/common/extHostMemento.ts index b49a1b434cafc..303e1ca90bc44 100644 --- a/src/vs/workbench/api/common/extHostMemento.ts +++ b/src/vs/workbench/api/common/extHostMemento.ts @@ -27,7 +27,7 @@ export class ExtensionMemento implements vscode.Memento { this._shared = global; this._storage = storage; - this._init = this._storage.getValue(this._shared, this._id, Object.create(null)).then(value => { + this._init = this._storage.initializeExtensionStorage(this._shared, this._id, Object.create(null)).then(value => { this._value = value; return this; }); diff --git a/src/vs/workbench/api/common/extHostStorage.ts b/src/vs/workbench/api/common/extHostStorage.ts index 01712256fed6c..1bd1365132b45 100644 --- a/src/vs/workbench/api/common/extHostStorage.ts +++ b/src/vs/workbench/api/common/extHostStorage.ts @@ -7,7 +7,7 @@ import { MainContext, MainThreadStorageShape, ExtHostStorageShape } from './extH import { Emitter } from 'vs/base/common/event'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { IExtensionIdWithVersion } from 'vs/platform/extensionManagement/common/extensionStorage'; export interface IStorageChangeEvent { shared: boolean; @@ -32,8 +32,8 @@ export class ExtHostStorage implements ExtHostStorageShape { this._proxy.$registerExtensionStorageKeysToSync(extension, keys); } - getValue(shared: boolean, key: string, defaultValue?: T): Promise { - return this._proxy.$getValue(shared, key).then(value => value || defaultValue); + initializeExtensionStorage(shared: boolean, key: string, defaultValue?: object): Promise { + return this._proxy.$initializeExtensionStorage(shared, key).then(value => value || defaultValue); } setValue(shared: boolean, key: string, value: object): Promise { diff --git a/src/vs/workbench/services/extensions/common/extensionStorageMigration.ts b/src/vs/workbench/services/extensions/common/extensionStorageMigration.ts new file mode 100644 index 0000000000000..7f5a4abb2efb3 --- /dev/null +++ b/src/vs/workbench/services/extensions/common/extensionStorageMigration.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getErrorMessage } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; +import { FileSystemProviderError, FileSystemProviderErrorCode, IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; + +/** + * An extension storage has following + * - State: Stored using storage service with extension id as key and state as value. + * - Resources: Stored under a location scoped to the extension. + */ +export async function migrateExtensionStorage(fromExtensionId: string, toExtensionId: string, storageMigratedKey: string, instantionService: IInstantiationService): Promise { + return instantionService.invokeFunction(async serviceAccessor => { + const environmentService = serviceAccessor.get(IEnvironmentService); + const extensionStorageService = serviceAccessor.get(IExtensionStorageService); + const storageService = serviceAccessor.get(IStorageService); + const uriIdentityService = serviceAccessor.get(IUriIdentityService); + const fileService = serviceAccessor.get(IFileService); + const workspaceContextService = serviceAccessor.get(IWorkspaceContextService); + const logService = serviceAccessor.get(ILogService); + + if (fromExtensionId === toExtensionId) { + return; + } + + const getExtensionStorageLocation = (extensionId: string, global: boolean): URI => { + if (global) { + return uriIdentityService.extUri.joinPath(environmentService.globalStorageHome, extensionId.toLowerCase() /* Extension id is lower cased for global storage */); + } + return uriIdentityService.extUri.joinPath(environmentService.workspaceStorageHome, workspaceContextService.getWorkspace().id, extensionId); + }; + + const migrateStorage = async (global: boolean) => { + // Migrate state + const value = extensionStorageService.getExtensionState(fromExtensionId, global); + if (value) { + extensionStorageService.setExtensionState(toExtensionId, value, global); + extensionStorageService.setExtensionState(fromExtensionId, undefined, global); + } + + // Migrate stored files + const fromPath = getExtensionStorageLocation(fromExtensionId, global); + const toPath = getExtensionStorageLocation(toExtensionId, global); + if (!uriIdentityService.extUri.isEqual(fromPath, toPath)) { + try { + await fileService.move(fromPath, toPath, true); + } catch (error) { + if ((error).code !== FileSystemProviderErrorCode.FileNotFound) { + logService.info(`Error while migrating ${global ? 'global' : 'workspace'} storage from '${fromExtensionId}' to '${toExtensionId}'`, getErrorMessage(error)); + } + } + } + }; + + // Migrate Global Storage + if (!storageService.getBoolean(storageMigratedKey, StorageScope.GLOBAL, false)) { + migrateStorage(true); + storageService.store(storageMigratedKey, true, StorageScope.GLOBAL, StorageTarget.MACHINE); + } + + // Migrate Workspace Storage + if (!storageService.getBoolean(storageMigratedKey, StorageScope.WORKSPACE, false)) { + migrateStorage(false); + storageService.store(storageMigratedKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + }); +} diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index 1fb4714891aa7..3db531110e53b 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { AbstractExtensionsInitializer, getExtensionStorageState, IExtensionsInitializerPreviewResult, storeExtensionStorageState } from 'vs/platform/userDataSync/common/extensionsSync'; +import { AbstractExtensionsInitializer, IExtensionsInitializerPreviewResult } from 'vs/platform/userDataSync/common/extensionsSync'; import { GlobalStateInitializer, UserDataSyncStoreTypeSynchronizer } from 'vs/platform/userDataSync/common/globalStateSync'; import { KeybindingsInitializer } from 'vs/platform/userDataSync/common/keybindingsSync'; import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSync'; @@ -34,6 +34,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); export interface IUserDataInitializationService { @@ -325,7 +326,7 @@ class InstalledExtensionsInitializer implements IUserDataInitializer { constructor( private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, - @IStorageService private readonly storageService: IStorageService, + @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, ) { } @@ -340,9 +341,9 @@ class InstalledExtensionsInitializer implements IUserDataInitializer { for (const installedExtension of preview.installedExtensions) { const syncExtension = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, installedExtension.identifier)); if (syncExtension?.state) { - const extensionState = getExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, this.storageService); + const extensionState = this.extensionStorageService.getExtensionState(installedExtension, true) || {}; Object.keys(syncExtension.state).forEach(key => extensionState[key] = syncExtension.state![key]); - storeExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, extensionState, this.storageService); + this.extensionStorageService.setExtensionState(installedExtension, extensionState, true); } } @@ -362,7 +363,7 @@ class NewExtensionsInitializer implements IUserDataInitializer { constructor( private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, @IExtensionService private readonly extensionService: IExtensionService, - @IStorageService private readonly storageService: IStorageService, + @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @@ -392,7 +393,7 @@ class NewExtensionsInitializer implements IUserDataInitializer { continue; } if (extensionToSync.state) { - storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService); + this.extensionStorageService.setExtensionState(galleryExtension, extensionToSync.state, true); } this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: extensionToSync.preRelease } /* set isMachineScoped to prevent install and sync dialog in web */); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index ee1856d0b2081..dc35c48aad597 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -121,14 +121,14 @@ import { DownloadService } from 'vs/platform/download/common/downloadService'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IIgnoredExtensionsManagementService, IgnoredExtensionsManagementService); registerSingleton(IGlobalExtensionEnablementService, GlobalExtensionEnablementService); -registerSingleton(IExtensionsStorageSyncService, ExtensionsStorageSyncService); +registerSingleton(IExtensionStorageService, ExtensionStorageService); registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(IContextViewService, ContextViewService, true); registerSingleton(IListService, ListService, true); From 3f29c4d34d66e49b5a857c269d2d5f30cda73726 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 7 Jan 2022 15:47:11 -0800 Subject: [PATCH 1160/2210] debug: add telemetry for users of the memory viewer --- .../workbench/contrib/debug/browser/variablesView.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index a8e6ed44dcdb8..1a15c0168d260 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -526,8 +526,20 @@ CommandsRegistry.registerCommand({ const notifications = accessor.get(INotificationService); const progressService = accessor.get(IProgressService); const extensionService = accessor.get(IExtensionService); + const telemetryService = accessor.get(ITelemetryService); + const debugService = accessor.get(IDebugService); + const ext = await extensionService.getExtension(HEX_EDITOR_EXTENSION_ID); if (ext || await tryInstallHexEditor(notifications, progressService, extensionService, commandService)) { + /* __GDPR__ + "debug/didViewMemory" : { + "debugType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + telemetryService.publicLog('debug/didViewMemory', { + debugType: debugService.getModel().getSession(arg.sessionId)?.configuration.type, + }); + await editorService.openEditor({ resource: getUriForDebugMemory(arg.sessionId, arg.variable.memoryReference), options: { From 3d85cffbb7b8812eec0443541853da06f5f9eeb7 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 7 Jan 2022 16:25:06 -0800 Subject: [PATCH 1161/2210] debug: open hex editor to side by default --- src/vs/workbench/contrib/debug/browser/variablesView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 1a15c0168d260..5b5e840983407 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -40,7 +40,7 @@ import { AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, CONTEXT_VARIABLES_FOCUSED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { ErrorScope, Expression, getUriForDebugMemory, Scope, StackFrame, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const $ = dom.$; @@ -546,7 +546,7 @@ CommandsRegistry.registerCommand({ revealIfOpened: true, override: HEX_EDITOR_EDITOR_ID, }, - }); + }, SIDE_GROUP); } } }); From 06c29c3610c7d71ba97f7688dd7a0e4e4f7a7b72 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 7 Jan 2022 16:25:28 -0800 Subject: [PATCH 1162/2210] debug: mark debug memory as deleted when session resumes --- .../contrib/debug/browser/debugMemory.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugMemory.ts b/src/vs/workbench/contrib/debug/browser/debugMemory.ts index 1686b569fa7e5..69225a04ebae5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugMemory.ts +++ b/src/vs/workbench/contrib/debug/browser/debugMemory.ts @@ -5,12 +5,12 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { clamp } from 'vs/base/common/numbers'; import { assertNever } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FileChangeType, FileOpenOptions, FilePermission, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileChange, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; -import { DEBUG_MEMORY_SCHEME, IDebugService, IDebugSession, IMemoryInvalidationEvent, IMemoryRegion, MemoryRange, MemoryRangeType } from 'vs/workbench/contrib/debug/common/debug'; +import { DEBUG_MEMORY_SCHEME, IDebugService, IDebugSession, IMemoryInvalidationEvent, IMemoryRegion, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; const rangeRe = /range=([0-9]+):([0-9]+)/; @@ -46,7 +46,15 @@ export class DebugMemoryFileSystemProvider implements IFileSystemProvider { } const { session, memoryReference, offset } = this.parseUri(resource); - return session.onDidInvalidateMemory(e => { + const disposable = new DisposableStore(); + + disposable.add(session.onDidChangeState(() => { + if (session.state === State.Running || session.state === State.Inactive) { + this.changeEmitter.fire([{ type: FileChangeType.DELETED, resource }]); + } + })); + + disposable.add(session.onDidInvalidateMemory(e => { if (e.body.memoryReference !== memoryReference) { return; } @@ -56,7 +64,9 @@ export class DebugMemoryFileSystemProvider implements IFileSystemProvider { } this.changeEmitter.fire([{ resource, type: FileChangeType.UPDATED }]); - }); + })); + + return disposable; } /** @inheritdoc */ From bf5219eeb987611ea791652d93f38199478d91ac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 7 Jan 2022 18:44:27 -0600 Subject: [PATCH 1163/2210] polish run recent command quickpick --- .../contrib/terminal/browser/terminalInstance.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 718e566eb2dbd..d566b94ab2c58 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -714,28 +714,30 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (label.length === 0) { continue; } - let description = ''; + let detail = ''; if (cwd) { - description += `cwd: ${cwd} `; + detail += `cwd: ${cwd} `; } if (exitCode) { // Since you cannot get the last command's exit code on pwsh, just whether it failed // or not, -1 is treated specially as simply failed if (exitCode === -1) { - description += 'failed'; + detail += 'failed'; } else { - description += `exitCode: ${exitCode}`; + detail += `exitCode: ${exitCode}`; } } + detail = detail.trim(); + const iconClass = exitCode ? `${ThemeIcon.asClassName(Codicon.x)}` : `${ThemeIcon.asClassName(Codicon.more)}`; const buttons: IQuickInputButton[] = [{ - iconClass: ThemeIcon.asClassName(Codicon.output), + iconClass, tooltip: nls.localize('viewCommandOutput', "View Command Output"), alwaysVisible: true }]; items.push({ label, - description: description.trim(), - detail: fromNow(timestamp, true), + description: fromNow(timestamp, true), + detail, id: timestamp.toString(), command: { command, timestamp, cwd, exitCode, getOutput }, buttons From 8611d268dce5dec28221ab5f8551d6ae471fa1f5 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 7 Jan 2022 17:01:50 -0800 Subject: [PATCH 1164/2210] fix issue with svg icons refs #132893 --- .../browser/parts/panel/media/basepanelpart.css | 11 ++++++++++- src/vs/workbench/browser/parts/panel/panelPart.ts | 8 +++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/panel/media/basepanelpart.css b/src/vs/workbench/browser/parts/panel/media/basepanelpart.css index 3d6e0f2561c85..77de064dc4643 100644 --- a/src/vs/workbench/browser/parts/panel/media/basepanelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/basepanelpart.css @@ -79,6 +79,11 @@ border-radius: 0px !important; } +.monaco-workbench .part.basepanel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { + width: 16px; + height: 16px; +} + .monaco-workbench .part.basepanel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::before, .monaco-workbench .part.basepanel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::after { content: ''; @@ -139,10 +144,14 @@ } .monaco-workbench .part.basepanel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label { - background: none !important; border-radius: 0; } +.monaco-workbench .part.basepanel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:not(.icon) .action-label, +.monaco-workbench .part.basepanel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.icon .action-label.codicon { + background: none !important; +} + .monaco-workbench .part.basepanel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:last-child { padding-right: 10px; } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 266460d0b0262..dfb14db9ca112 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -327,12 +327,14 @@ export abstract class BasePanelPart extends CompositePart impleme const hash = new StringSHA1(); hash.update(cssUrl); cssClass = `activity-${id.replace(/\./g, '-')}-${hash.digest()}`; - const iconClass = `.monaco-workbench .panel .monaco-action-bar .action-label.${cssClass}`; + const iconClass = `.monaco-workbench .basepanel .monaco-action-bar .action-label.${cssClass}`; createCSSRule(iconClass, ` mask: ${cssUrl} no-repeat 50% 50%; - mask-size: 24px; + mask-size: 16px; -webkit-mask: ${cssUrl} no-repeat 50% 50%; - -webkit-mask-size: 24px; + -webkit-mask-size: 16px; + mask-origin: padding; + -webkit-mask-origin: padding; `); } else if (ThemeIcon.isThemeIcon(icon)) { cssClass = ThemeIcon.asClassName(icon); From 7788a6f831bcfe1876705c63e8dab748d9df5f84 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 7 Jan 2022 17:40:34 -0800 Subject: [PATCH 1165/2210] do not merge input and preview matches in markup cells --- .../notebook/index.ts | 5 ---- .../browser/contrib/find/findModel.ts | 3 +-- .../notebook/browser/notebookEditorWidget.ts | 23 ++++++++++++++----- .../view/renderers/backLayerWebView.ts | 8 +++---- .../browser/view/renderers/webviewPreloads.ts | 15 ++++++++---- .../contrib/notebook/common/notebookCommon.ts | 2 +- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index efad92ad43704..fe25756c0ea37 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -182,11 +182,6 @@ export const activate: ActivationFunction = (ctx) => { const defaultStyles = document.getElementById('_defaultStyles') as HTMLStyleElement; previewRoot.appendChild(defaultStyles.cloneNode(true)); - const findAnchor = document.createElement('div'); - findAnchor.id = '_defaultFindAnchor'; - findAnchor.tabIndex = -1; - previewRoot.appendChild(findAnchor); - // And then contributed styles for (const element of document.getElementsByClassName('markdown-style')) { if (element instanceof HTMLTemplateElement) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index 90091d62edc53..c3fcdcc64f7f3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -122,7 +122,6 @@ export class FindModel extends Disposable { const findMatch = this._findMatches[cellIndex]; if (matchIndex >= findMatch.modelMatchCount) { // reveal output range - findMatch.cell.updateEditState(CellEditState.Editing, 'find'); this._notebookEditor.focusElement(findMatch.cell); const index = this._notebookEditor.getCellIndex(findMatch.cell); if (index !== undefined) { @@ -308,7 +307,7 @@ export class FindModel extends Disposable { const val = this._state.searchString; const wordSeparators = this._configurationService.inspect('editor.wordSeparators').value; - const options: INotebookSearchOptions = { regex: this._state.isRegex, wholeWord: this._state.wholeWord, caseSensitive: this._state.matchCase, wordSeparators: wordSeparators, includeOutputs: !!this._state.filters }; + const options: INotebookSearchOptions = { regex: this._state.isRegex, wholeWord: this._state.wholeWord, caseSensitive: this._state.matchCase, wordSeparators: wordSeparators, includePreview: !!this._state.filters }; if (!val) { ret = null; } else if (!this._notebookEditor.hasModel()) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 1488629b6c059..6a30da5e0ee9b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2345,20 +2345,29 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } private async _warmupAll() { - if (!this.hasModel()) { + if (!this.hasModel() || !this.viewModel) { return; } - const renderPromises = []; + const cells = this.viewModel.viewCells; + const markupRequests = []; + + for (let i = 0; i < cells.length; i++) { + if (cells[i].cellKind === CellKind.Markup && !this._webview!.markupPreviewMapping.has(cells[i].id)) { + markupRequests.push(this.createMarkupPreview(cells[i])); + } + } + + const renderOutputPromises = []; for (let i = 0; i < this.getLength(); i++) { const cell = this.cellAt(i); if (cell?.cellKind === CellKind.Code) { - renderPromises.push(this._renderCell((cell as CodeCellViewModel))); + renderOutputPromises.push(this._renderCell((cell as CodeCellViewModel))); } } - return Promise.all(renderPromises); + return Promise.all([...markupRequests, ...renderOutputPromises]); } async find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup: boolean = false): Promise { @@ -2367,7 +2376,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } const findMatches = this._notebookViewModel.find(query, options).filter(match => match.matches.length > 0); - if (!options.includeOutputs) { + if (!options.includePreview) { // clear output matches await this._webview?.findStop(); return findMatches; @@ -2375,7 +2384,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const matchMap: { [key: string]: CellFindMatchWithIndex } = {}; findMatches.forEach(match => { - matchMap[match.cell.id] = match; + if (match.cell.cellKind === CellKind.Code) { + matchMap[match.cell.id] = match; + } }); if (this._webview) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 98931bd1d1714..ac5c5d438e813 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -264,7 +264,7 @@ export class BackLayerWebView extends Disposable { } /* markdown */ - #container > div.preview { + #container div.preview { width: 100%; padding-right: var(--notebook-preview-node-padding); padding-left: var(--notebook-markdown-left-margin); @@ -280,18 +280,18 @@ export class BackLayerWebView extends Disposable { color: var(--theme-ui-foreground); } - #container > div.preview.draggable { + #container div.preview.draggable { user-select: none; -webkit-user-select: none; -ms-user-select: none; cursor: grab; } - #container > div.preview.selected { + #container div.preview.selected { background: var(--theme-notebook-cell-selected-background); } - #container > div.preview.dragging { + #container div.preview.dragging { background-color: var(--theme-background); opacity: 0.5 !important; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index e54ccb183dc36..166fa841cd32c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -771,7 +771,7 @@ async function webviewPreloads(ctx: PreloadContext) { window.addEventListener('wheel', handleWheel); interface IFindMatch { - type: 'input' | 'output', + type: 'preview' | 'output', id: string, cellId: string, container: Node, @@ -787,7 +787,7 @@ async function webviewPreloads(ctx: PreloadContext) { const find = (query: string) => { let find = true; let matches: { - type: 'input' | 'output', + type: 'preview' | 'output', id: string, cellId: string, container: Node, @@ -819,14 +819,14 @@ async function webviewPreloads(ctx: PreloadContext) { } if (selection.getRangeAt(0).startContainer.nodeType === 1 - && (selection.getRangeAt(0).startContainer as Element).classList.contains('widgetarea')) { + && (selection.getRangeAt(0).startContainer as Element).classList.contains('markup')) { // markdown preview container const preview = (selection.anchorNode?.firstChild as Element); const root = preview.shadowRoot as ShadowRoot & { getSelection: () => Selection }; const shadowSelection = root?.getSelection ? root?.getSelection() : null; if (shadowSelection && shadowSelection.anchorNode) { matches.push({ - type: 'input', + type: 'preview', id: preview.id, cellId: preview.id, container: preview, @@ -1580,6 +1580,10 @@ async function webviewPreloads(ctx: PreloadContext) { this.ready = new Promise(r => resolveReady = r); const root = document.getElementById('container')!; + const markupCell = document.createElement('div'); + markupCell.className = 'markup'; + markupCell.style.position = 'absolute'; + markupCell.style.width = '100%'; this.element = document.createElement('div'); this.element.id = this.id; @@ -1587,7 +1591,8 @@ async function webviewPreloads(ctx: PreloadContext) { this.element.style.position = 'absolute'; this.element.style.top = top + 'px'; this.toggleDragDropEnabled(currentOptions.dragAndDropEnabled); - root.appendChild(this.element); + markupCell.appendChild(this.element); + root.appendChild(markupCell); this.addEventListeners(); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 065f3629eda93..988d9b2d329c5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -824,7 +824,7 @@ export interface INotebookSearchOptions { wholeWord?: boolean; caseSensitive?: boolean; wordSeparators?: string; - includeOutputs?: boolean; + includePreview?: boolean; } export interface INotebookExclusiveDocumentFilter { From d45f3c7b464e0454780c2e1b1238815e65545cf8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sat, 8 Jan 2022 03:39:27 +0100 Subject: [PATCH 1166/2210] Fix #139628 --- .../common/extensionGalleryService.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 5e8e619ea50a8..4242a00711731 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -513,7 +513,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const id = getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName); return { rawGalleryExtension, version: (identifiers.find(identifier => areSameExtensions(identifier, { id })))?.version, preRelease: includePreRelease }; }); - return this.converToGalleryExtensions(galleryExtensionsByVersion, CURRENT_TARGET_PLATFORM, () => undefined, token); + return this.converToGalleryExtensions(galleryExtensionsByVersion, query, CURRENT_TARGET_PLATFORM, () => undefined, token); } async getExtensions2(identifiers: ReadonlyArray): Promise { @@ -542,7 +542,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return { rawGalleryExtension, preRelease: !!identifier?.preRelease }; }); - return this.converToGalleryExtensions(rawGalleryExtensionsInput, CURRENT_TARGET_PLATFORM, () => undefined, CancellationToken.None); + return this.converToGalleryExtensions(rawGalleryExtensionsInput, query, CURRENT_TARGET_PLATFORM, () => undefined, CancellationToken.None); } async getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise { @@ -685,25 +685,26 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const { galleryExtensions, total } = await this.queryGallery(query, CURRENT_TARGET_PLATFORM, token); const telemetryData = (index: number) => ({ index: ((query.pageNumber - 1) * query.pageSize) + index, querySource: options.source }); - const extensions = await this.converToGalleryExtensions(galleryExtensions.map(rawGalleryExtension => ({ rawGalleryExtension, preRelease: !!options.includePreRelease })), CURRENT_TARGET_PLATFORM, telemetryData, token); + const extensions = await this.converToGalleryExtensions(galleryExtensions.map(rawGalleryExtension => ({ rawGalleryExtension, preRelease: !!options.includePreRelease })), query, CURRENT_TARGET_PLATFORM, telemetryData, token); const getPage = async (pageIndex: number, ct: CancellationToken) => { if (ct.isCancellationRequested) { throw canceled(); } const nextPageQuery = query.withPage(pageIndex + 1); const { galleryExtensions } = await this.queryGallery(nextPageQuery, CURRENT_TARGET_PLATFORM, ct); - return await this.converToGalleryExtensions(galleryExtensions.map(rawGalleryExtension => ({ rawGalleryExtension, preRelease: !!options.includePreRelease })), CURRENT_TARGET_PLATFORM, telemetryData, token); + return await this.converToGalleryExtensions(galleryExtensions.map(rawGalleryExtension => ({ rawGalleryExtension, preRelease: !!options.includePreRelease })), nextPageQuery, CURRENT_TARGET_PLATFORM, telemetryData, token); }; return { firstPage: extensions, total, pageSize: query.pageSize, getPage } as IPager; } - private async converToGalleryExtensions(rawGalleryExtensions: { rawGalleryExtension: IRawGalleryExtension, preRelease: boolean, version?: string }[], targetPlatform: TargetPlatform, telemetryData: (index: number) => IStringDictionary | undefined, token: CancellationToken): Promise { - const toExtensionWithLatestVersion = (galleryExtension: IRawGalleryExtension, index: number, hasReleaseVersion: boolean, preRelease: boolean): IGalleryExtension => { + private async converToGalleryExtensions(rawGalleryExtensions: { rawGalleryExtension: IRawGalleryExtension, preRelease: boolean, version?: string }[], query: Query, targetPlatform: TargetPlatform, telemetryData: (index: number) => IStringDictionary | undefined, token: CancellationToken): Promise { + const toExtensionWithLatestVersion = (galleryExtension: IRawGalleryExtension, index: number, query: Query, hasReleaseVersion: boolean, preRelease: boolean): IGalleryExtension => { + const hasAllVersions: boolean = !(query.flags & Flags.IncludeLatestVersionOnly); const allTargetPlatforms = getAllTargetPlatforms(galleryExtension); let latestVersion = galleryExtension.versions[0]; latestVersion = galleryExtension.versions.find(version => version.version === latestVersion.version && isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(version), allTargetPlatforms, targetPlatform)) || latestVersion; - if (isPreReleaseVersion(latestVersion) && !preRelease) { + if (hasAllVersions && isPreReleaseVersion(latestVersion) && !preRelease) { latestVersion = galleryExtension.versions.find(version => version.version !== latestVersion.version && !isPreReleaseVersion(version)) || latestVersion; } return toExtension(galleryExtension, latestVersion, allTargetPlatforms, hasReleaseVersion, telemetryData(index)); @@ -719,7 +720,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi result.push([index, toExtension(rawGalleryExtension, versionAsset, getAllTargetPlatforms(rawGalleryExtension), hasReleaseVersion)]); } } else { - const extension = toExtensionWithLatestVersion(rawGalleryExtension, index, hasReleaseVersion, preRelease); + const extension = toExtensionWithLatestVersion(rawGalleryExtension, index, query, hasReleaseVersion, preRelease); if (extension.properties.isPreReleaseVersion) { preReleaseVersions.set(extension.identifier.uuid, { index, preRelease }); } else { @@ -745,7 +746,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi for (const rawGalleryExtension of galleryExtensions) { const hasReleaseVersion = rawGalleryExtension.versions.some(version => !isPreReleaseVersion(version)); const { index, preRelease } = preReleaseVersions.get(rawGalleryExtension.extensionId)!; - const extension = toExtensionWithLatestVersion(rawGalleryExtension, index, hasReleaseVersion, preRelease); + const extension = toExtensionWithLatestVersion(rawGalleryExtension, index, query, hasReleaseVersion, preRelease); result.push([index, extension]); } } From 4c302e28608f025e7090278e5577f0e7ad46bdc9 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 7 Jan 2022 23:03:44 -0800 Subject: [PATCH 1167/2210] fix match update overflow --- .../contrib/notebook/browser/contrib/find/findModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index c3fcdcc64f7f3..8342a27250332 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -260,7 +260,7 @@ export class FindModel extends Disposable { } } else { // output now has the highlight - const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= oldCurrMatchCellIndex); + const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.index), index => index >= oldCurrMatchCellIndex) % findMatches.length; this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection)); } } From 5384edc64b6b0eb71b177a6c5f0cd34e3ebb1a8e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 8 Jan 2022 10:05:53 +0100 Subject: [PATCH 1168/2210] :up: distro @sbatten your change broke the build --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c0ef9d12dd2e1..bcc6259cf30f9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.64.0", - "distro": "3840fb71930c0410ea53305aef2f3d2af8e9b621", + "distro": "b1264f356b1169e891dd4be6a0ac1fdfcd3312cf", "author": { "name": "Microsoft Corporation" }, From 07a9c5bfe18b1c6fa3b5e9ae300468ff28719df4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 8 Jan 2022 10:15:52 +0100 Subject: [PATCH 1169/2210] fix compile errors @sbatten another build failure --- src/vs/platform/environment/test/node/nativeModules.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/environment/test/node/nativeModules.test.ts b/src/vs/platform/environment/test/node/nativeModules.test.ts index 7f154f11b5a4c..1d4b01900b754 100644 --- a/src/vs/platform/environment/test/node/nativeModules.test.ts +++ b/src/vs/platform/environment/test/node/nativeModules.test.ts @@ -90,7 +90,7 @@ flakySuite('Native Modules (all platforms)', () => { }); test('vscode-windows-registry', async () => { - const windowsRegistry = await import('vscode-windows-registry'); + const windowsRegistry = await import('@vscode/windows-registry'); assert.ok(typeof windowsRegistry.GetStringRegKey === 'function', testErrorMessage('vscode-windows-registry')); }); From 61c35c63c34afaac1e90c1d68013583bc85c25a2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 8 Jan 2022 11:21:11 +0100 Subject: [PATCH 1170/2210] smoke - add some logging (#140324) --- .../platform/driver/electron-main/driver.ts | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 0a193453e0fa3..b29a9d5fc08e5 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -24,6 +24,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { join } from 'vs/base/common/path'; import { VSBuffer } from 'vs/base/common/buffer'; +import { ILogService } from 'vs/platform/log/common/log'; function isSilentKeyCode(keyCode: KeyCode) { return keyCode < KeyCode.Digit0; @@ -43,10 +44,13 @@ export class Driver implements IDriver, IWindowDriverRegistry { @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IFileService private readonly fileService: IFileService, - @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService + @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, + @ILogService private readonly logService: ILogService ) { } async registerWindowDriver(windowId: number): Promise { + this.logService.info(`[driver] registerWindowDriver(${windowId})`); + this.registeredWindowIds.add(windowId); this.reloadingWindowIds.delete(windowId); this.onDidReloadingChange.fire(); @@ -54,22 +58,29 @@ export class Driver implements IDriver, IWindowDriverRegistry { } async reloadWindowDriver(windowId: number): Promise { + this.logService.info(`[driver] reloadWindowDriver(${windowId})`); + this.reloadingWindowIds.add(windowId); } async getWindowIds(): Promise { - return this.windowsMainService.getWindows() - .map(w => w.id) - .filter(id => this.registeredWindowIds.has(id) && !this.reloadingWindowIds.has(id)); + this.logService.info(`[driver] getWindowIds(): begin`); + + const windowIds = this.windowsMainService.getWindows() + .map(window => window.id) + .filter(windowId => this.registeredWindowIds.has(windowId) && !this.reloadingWindowIds.has(windowId)); + + this.logService.info(`[driver] getWindowIds(): end (windowIds: ${windowIds.join(', ')})`); + + return windowIds; } async capturePage(windowId: number): Promise { - await this.whenUnfrozen(windowId); - const window = this.windowsMainService.getWindowById(windowId) ?? this.windowsMainService.getLastActiveWindow(); // fallback to active window to ensure we capture window if (!window?.win) { throw new Error('Invalid window'); } + const webContents = window.win.webContents; const image = await webContents.capturePage(); return image.toPNG().toString('base64'); @@ -91,17 +102,22 @@ export class Driver implements IDriver, IWindowDriverRegistry { } async reloadWindow(windowId: number): Promise { + this.logService.info(`[driver] reloadWindow(${windowId})`); + await this.whenUnfrozen(windowId); const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } + this.reloadingWindowIds.add(windowId); this.lifecycleMainService.reload(window); } exitApplication(): Promise { + this.logService.info(`[driver] exitApplication()`); + return this.lifecycleMainService.quit(); } @@ -219,6 +235,8 @@ export class Driver implements IDriver, IWindowDriverRegistry { } private async getWindowDriver(windowId: number): Promise { + this.logService.info(`[driver] getWindowDriver(${windowId})`); + await this.whenUnfrozen(windowId); const id = `window:${windowId}`; @@ -228,9 +246,13 @@ export class Driver implements IDriver, IWindowDriverRegistry { } private async whenUnfrozen(windowId: number): Promise { + this.logService.info(`[driver] whenUnfrozen(${windowId}): begin`); + while (this.reloadingWindowIds.has(windowId)) { await Event.toPromise(this.onDidReloadingChange.event); } + + this.logService.info(`[driver] whenUnfrozen(${windowId}): end`); } } From b4c99b01efe14ae10906f01d04081db3ccfdf2c4 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sat, 8 Jan 2022 08:08:24 -0800 Subject: [PATCH 1171/2210] fixes #140287 --- src/vs/workbench/browser/layoutState.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/layoutState.ts b/src/vs/workbench/browser/layoutState.ts index 4915cee32177c..c3d9578825aa5 100644 --- a/src/vs/workbench/browser/layoutState.ts +++ b/src/vs/workbench/browser/layoutState.ts @@ -146,6 +146,9 @@ export class LayoutStateModel extends Disposable { } } + // Apply behavioral setting + this.stateCache.set(LayoutStateKeys.PANEL_POSITION.name, positionFromString(this.configurationService.getValue(WorkbenchLayoutSettings.PANEL_DEFAULT_POSITION) ?? 'bottom')); + // Apply sizing defaults const workbenchDimensions = getClientArea(this.container); const panelPosition = this.stateCache.get(LayoutStateKeys.PANEL_POSITION.name) ?? LayoutStateKeys.PANEL_POSITION.defaultValue; @@ -254,6 +257,7 @@ export class LayoutStateModel extends Disposable { } export enum WorkbenchLayoutSettings { + PANEL_DEFAULT_POSITION = 'workbench.panel.defaultLocation', PANEL_OPENS_MAXIMIZED = 'workbench.panel.opensMaximized', ZEN_MODE_CONFIG = 'zenMode', ZEN_MODE_SILENT_NOTIFICATIONS = 'zenMode.silentNotifications', @@ -261,7 +265,6 @@ export enum WorkbenchLayoutSettings { } enum LegacyWorkbenchLayoutSettings { - PANEL_POSITION = 'workbench.panel.defaultLocation', // Deprecated to UI State ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', // Deprecated to UI State STATUSBAR_VISIBLE = 'workbench.statusBar.visible', // Deprecated to UI State SIDEBAR_POSITION = 'workbench.sideBar.location', // Deprecated to UI State From 5b488cc2f87acb78a658ff3c84555b6ca33aaa80 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sat, 8 Jan 2022 08:33:31 -0800 Subject: [PATCH 1172/2210] update panel position setting behavior --- src/vs/workbench/browser/layoutState.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/layoutState.ts b/src/vs/workbench/browser/layoutState.ts index c3d9578825aa5..4c14e834e062f 100644 --- a/src/vs/workbench/browser/layoutState.ts +++ b/src/vs/workbench/browser/layoutState.ts @@ -64,7 +64,7 @@ export const LayoutStateKeys = { // Part Positions SIDEBAR_POSITON: new RuntimeStateKey('sideBar.position', StorageScope.GLOBAL, StorageTarget.USER, Position.LEFT), - PANEL_POSITION: new RuntimeStateKey('panel.position', StorageScope.WORKSPACE, StorageTarget.USER, Position.BOTTOM), + PANEL_POSITION: new RuntimeStateKey('panel.position', StorageScope.GLOBAL, StorageTarget.USER, Position.BOTTOM), PANEL_ALIGNMENT: new RuntimeStateKey('panel.alignment', StorageScope.GLOBAL, StorageTarget.USER, 'center'), // Part Visibility @@ -114,6 +114,10 @@ export class LayoutStateModel extends Disposable { if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION)) { this.setRuntimeValueAndFire(LayoutStateKeys.SIDEBAR_POSITON, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left')); } + + if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.PANEL_POSITION)) { + this.setRuntimeValueAndFire(LayoutStateKeys.PANEL_POSITION, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.PANEL_POSITION) ?? 'bottom')); + } } private updateLegacySettingsFromState(key: RuntimeStateKey, value: T): void { @@ -130,6 +134,8 @@ export class LayoutStateModel extends Disposable { this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.PANEL_ALIGNMENT, value); } else if (key === LayoutStateKeys.SIDEBAR_POSITON) { this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, positionToString(value as Position)); + } else if (key === LayoutStateKeys.PANEL_POSITION) { + this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.PANEL_POSITION, positionToString(value as Position)); } } @@ -146,8 +152,12 @@ export class LayoutStateModel extends Disposable { } } - // Apply behavioral setting - this.stateCache.set(LayoutStateKeys.PANEL_POSITION.name, positionFromString(this.configurationService.getValue(WorkbenchLayoutSettings.PANEL_DEFAULT_POSITION) ?? 'bottom')); + // Apply legacy settings + this.stateCache.set(LayoutStateKeys.ACTIVITYBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE)); + this.stateCache.set(LayoutStateKeys.STATUSBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE)); + this.stateCache.set(LayoutStateKeys.PANEL_ALIGNMENT.name, this.configurationService.getValue(LegacyWorkbenchLayoutSettings.PANEL_ALIGNMENT)); + this.stateCache.set(LayoutStateKeys.SIDEBAR_POSITON.name, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left')); + this.stateCache.set(LayoutStateKeys.PANEL_POSITION.name, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.PANEL_POSITION) ?? 'bottom')); // Apply sizing defaults const workbenchDimensions = getClientArea(this.container); @@ -164,10 +174,6 @@ export class LayoutStateModel extends Disposable { applySizingIfUndefined(LayoutStateKeys.PANEL_SIZE, panelPosition === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4); // Apply legacy settings - this.stateCache.set(LayoutStateKeys.ACTIVITYBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE)); - this.stateCache.set(LayoutStateKeys.STATUSBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE)); - this.stateCache.set(LayoutStateKeys.PANEL_ALIGNMENT.name, this.configurationService.getValue(LegacyWorkbenchLayoutSettings.PANEL_ALIGNMENT)); - this.stateCache.set(LayoutStateKeys.SIDEBAR_POSITON.name, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left')); // Apply all defaults for (key in LayoutStateKeys) { @@ -257,7 +263,6 @@ export class LayoutStateModel extends Disposable { } export enum WorkbenchLayoutSettings { - PANEL_DEFAULT_POSITION = 'workbench.panel.defaultLocation', PANEL_OPENS_MAXIMIZED = 'workbench.panel.opensMaximized', ZEN_MODE_CONFIG = 'zenMode', ZEN_MODE_SILENT_NOTIFICATIONS = 'zenMode.silentNotifications', @@ -265,6 +270,7 @@ export enum WorkbenchLayoutSettings { } enum LegacyWorkbenchLayoutSettings { + PANEL_POSITION = 'workbench.panel.defaultLocation', // Deprecated to UI State ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', // Deprecated to UI State STATUSBAR_VISIBLE = 'workbench.statusBar.visible', // Deprecated to UI State SIDEBAR_POSITION = 'workbench.sideBar.location', // Deprecated to UI State From e5054d1d42fb0d3ea6f5b4ba95545c26375847eb Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sat, 8 Jan 2022 08:36:49 -0800 Subject: [PATCH 1173/2210] update on settings change --- src/vs/workbench/browser/layout.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index b627088d5bbae..17d638c849ded 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -428,6 +428,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.setSideBarPosition(change.value as Position); } + if (change.key === LayoutStateKeys.PANEL_POSITION) { + this.setPanelPosition(change.value as Position); + } + if (change.key === LayoutStateKeys.PANEL_ALIGNMENT) { this.setPanelAlignment(change.value as PanelAlignment); } From f528f1a415557a530a36a44a8b2675b6bd2575a0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 7 Jan 2022 13:26:13 -0800 Subject: [PATCH 1174/2210] Make sure markdown refresh forces the preview to be updated --- extensions/markdown-language-features/src/features/preview.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 0ff1bf1915606..408c0d47b3bc8 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -595,7 +595,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown } public refresh() { - this.preview.refresh(); + this.preview.refresh(true); } public updateConfiguration() { @@ -748,7 +748,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow } public refresh() { - this._preview.refresh(); + this._preview.refresh(true); } public updateConfiguration() { From 6b51d55ff1dab78ddfbfba0dbbd35e25e5e4f68e Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 9 Jan 2022 13:10:42 -0800 Subject: [PATCH 1175/2210] enable hybrid search in code --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a896b7e5a7bee..e670e7ff24c05 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -100,5 +100,6 @@ "list", "git", "sash" - ] + ], + "notebook.find.experimental": true } From 401039f10401824d9744c1e347b2b314dc63b195 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 10 Jan 2022 00:24:29 +0100 Subject: [PATCH 1176/2210] Renames breakpointHit.webm to breakpoint.webm. --- .../audioCues/browser/audioCueContribution.ts | 2 +- .../media/{breakpointHit.webm => breakpoint.webm} | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename src/vs/workbench/contrib/audioCues/browser/media/{breakpointHit.webm => breakpoint.webm} (100%) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts index 0e4d35cf9d2c7..2a4a0d58fdcd3 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts @@ -72,7 +72,7 @@ export class AudioCueContribution extends DisposableStore implements IWorkbenchC } public handleBreakpointOnLine(): void { - this.playSound('breakpointHit'); + this.playSound('breakpoint'); } private async playSound(fileName: string) { diff --git a/src/vs/workbench/contrib/audioCues/browser/media/breakpointHit.webm b/src/vs/workbench/contrib/audioCues/browser/media/breakpoint.webm similarity index 100% rename from src/vs/workbench/contrib/audioCues/browser/media/breakpointHit.webm rename to src/vs/workbench/contrib/audioCues/browser/media/breakpoint.webm From 594c5a1e566b654e5c30901489868b9617d2edea Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 10 Jan 2022 00:25:11 +0100 Subject: [PATCH 1177/2210] Updates sounds. --- .../audioCues/browser/media/breakpoint.webm | Bin 11224 -> 8185 bytes .../audioCues/browser/media/error.webm | Bin 10910 -> 7849 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/media/breakpoint.webm b/src/vs/workbench/contrib/audioCues/browser/media/breakpoint.webm index 030f938a0091bfa4b5bf17353922615d0849cf85..d83736177b7a9f5ec83ba849cd93eecdfcd1e915 100644 GIT binary patch literal 8185 zcmeHMdpuNW+<#_Va;+h?W189_$_NdHrByMRTxuGI3At1*F`|UYrQ4a@reTF-OI9*N zC_+(bld|3AnoEf;S`yo~v@0d^KIe?u_WkF5Kc9Wyf8Nh~Jm+|>zsvXcJHO}moFOrv zicd0<1vth8yvGFcjB$Yy<6vS;kbgKMMSy1{3GhTWBp3(K>)X2;tP|07tShN*XAx-<&3CWZ4qH8}D`{{{)4eg+?d(B9uB=IRy8;IfetUjYMhAFdopFN&DMM0*N@83krXh5-Ufo!3@$(yq}au3 zC5r5nbQ1MfdS)doT?uPKYuLv_`v{EB52kep9JNzgm^eVQQcL*N>ODD;Y3)gXeXl5q za~PL$6V6)0$$A!!E(wa(ugHlvj0{8vf1zEPpD1r(kaqAT?asplRfaG(!O7x1DKXeC zcD{^F(TC=cqfdD8KSc!@}MMWW^Az`{wV)9u$Kmd@3Z0Kv;Y#g%fTim(bwc)*A74MuN zZEQKjkkHQCR;nnZ$!RNMc7S3a8Y0>+$Y+odqVw!R<#Ow%c>@JrvvZxRtQY1v*2HC< z5M8z&%8P2gJD6wM3~PmXUTV2;-ca5z1zsw$+S01H)?7ohZ&&m1mOPDHu(qT;E+hAc z8dy^(=cVVatAaJh%W<3&>(IU^loa-OeRG(cHy3S|T)mugqV*=Mv2P9c=Mlb4!RBEg zLw#)t$v4JmKhNUHiX=eA0&NpF?6{Ba*&;DM`^cTXKW#zwc+`=uoJh#8G z9?DI)uyND&@*0jawJ4jTg5gJj*fSKh12bGv%Kk#?>AXl4!x9;s6x1{Tu*b-48Vmm} ze~?==^j$S!G43$4=x}3l`9a+|e6Nf9ynB*-C-(Kh@}9rdGbhOFA4R4*9smk-c9|m7 zTg0d)>sNZ&gwJY1gMtVx2h87`s`b{j>pf^UVvp0=b62`*F+1qowt2p;--Wwc26b5Y zx>@>iH->xUgnQTWdw&e-o!IyEtKvHgQ&AIu6m=2aX<-11lK1h#HD6xzSXQ8EJICoi5X{ZSG14s7JIcXv`P@ z0JXc7TKC(?EKU7RjxpEGxRb-|bmRV47psHLnhSwd1{pYzL2xrRzLl$-s^{MxdZr*i zjWb$Q^dkS-~v^yM9bGg|06A_DW+k_K~@P}g3gSo!Z&U)Nw{Cc=bFiFQiui%eFcARIsj zta4yZkUroEvnS6SeGvd#ymAd-p#OK*KN)HQAs$2+K!&%gjGjS`dUvZqjzX&qQjCMz zz{v)vbd`2){<{zu`0>CM0L7bCMy$EaED0}nsYuVM!-lQJ%N>f#=iL~$LDen{$t5sd z>8xYLyfQ-?i#reIE6-5a6N-O%0g{r>$^!--kVXDpxr1@}OGU+{xiTuuHag3V>y%X- zmk*OAUT$$&wGnIF2Hm3Ex!k0g?y|DikP%x8&gORVpQ98Y$FkRT7}E}S7MJf!5f$@t z%gV|sRPEVXkfv`pUodp2kXKfArp|=GLdo%cy%bRi%SP^**>WKzUymFdx@4rTTFr&q z4aakW0NOIT4^f{@hS_mi!I3n>)XKI*)j77Obe7=}0{|L`#aeFnXiIb?1kx?NL@mY= z{i1eG8AZb3Q0FDOI~(eYvpJzuE`dWW%~oL;>L29*&?At8calrU9cMt90}$F;bkp9T zE71yQwqB~i5t$C<9dR}tglUiyRU7W+Ly~MP<>5G^eb&?>EbqzYIKxb7gELh>vDt2E zQFc4bkLCj4j-f5SGbq2<4uhM618V63DBKXK96iih2D!ycoMZ+lyZaf>qtJ{f^ks(K z^I%@U;&OefjW8@<*U?beAS^P_>P*7CQPhI8Ys;ZUAZ9+@{GdlU0;4MxqmQcrcq=~( z6yO(Zbg-h5RE_9?`sjXXOH9asDR~fqp`w$R%~67H#aHgH2^^uzr&V>K300SEFnquo z(u6htesIvyznlN9nlVhyung_1hXVM(wF2)~_f)R9;ne221zHq`KzDB-77?S=7}O$n zC1`Rx)SDXvT&am1j<|86epc_NdFhJZkg7ES#En9L zP6B-fdWD3OW(~b!Hbd!@JM6=1I@xKc_h9EAjx+4TY8jl@Gc|%U^(S-%dPJfYC19fj zXM-Y%*35~iRFaL>I1H#QG!!;ZUjj2+v`^_2G@dgx2`($jhALBwG@X_86E!(hYLS++ zk`5e%eeTZ6rxG=xbE4Qo0?t%uU9uHSj)`_lwYl@Rij}`vB#SD6Fy|@E=1`592X%{x z65_hG^P*ffiAo~XI1frW&6GjC9HD7q_7QipK`n+F<29MTo~mekl*qV6l=MWdI~6q> zMnyY-AQ=G2$t#$HhG*b;6d@Wf@2~(M6#H#L`$k4Ytcx;1!wqNae~@7U+&z6HzCN}; z;!Iw>sF2f<*HN-Fnq7d%lk*}P=kAQXp}wJ&YRIQRx-jB_w@^SIprO&$v^6j!EFwB~ zFOn!s%YXqAo|RO=UU+iVx3#yYM-jD23&;x>eM`~NU91PgI6P9ri-G`od09qbb}fCS%>HGno#EMjh0!?tJIZ)SMeryO#L+(~iYxic*4%akQdaWyy&edntKH z-x6n}h#DC*v{jzAtg#U3`fsY~SlpMtg4G+E{p`rKE+8Hj08M27qp1T7a@>`3_a{c< zlJzIG-yCn2F162^_*fTvxS#F4baUgl-yxnwi938zz}XGXM|1HaD+kkyoMS3@+_P*~ zd|2mSIZ70J*TT2DM_!&k>*0&z6$&0BE008-s3K3WNy z+0tNcV=k7m*XsN}w8CwoBR2Wth?&S4UtL&hSEPc<-O>3Qp7Pj*^pmB%vthepMy;HZ z9IZ*UP%T*~(8n!r>|%5ttK00?fD^wEfTf7vB!pwgMw~h_R_}e%?w+~LvQyF)?tSyb zOD6qK;~!^DsZGSJt>C(7({$i^2 z@!PIJnYiVB9wW$F+&u9^0Wd@QOQsIw_maIjMxM09E9O6cbZP3qqF!>`F#k>PO1YMZ z16-G#naJ5ddlV@E77du<3v>xRnK=2{2%;R(#XwKq*kGGij^NmpqWNkSisIh{zy?_b z4Pe4co_feX8c%A8I2<;;`bVQcE55H}#{en+M}<#muN+Zw4-=NyYn!TNJ#NeCI#BQQO5&OAn^SCkKcADfL(3t?q7noPX+qP2)IMD9>b3(=%NSAx{d0rBn1P zMG)(5W`-!q-DtQ2RszCJ1N-7xTYzorvTxEHDw*9GhMv?2e&^a76Gt~39y}Dq$qWy{ z%@V&901h%u{dAzYOHF(K!q+^X#&Pox(ma+I=RH3uK3NnTTR*!-*~?9H;{zou%={UE z&y=u^1f}(7`xIHW`RuH`o*49fEU^9c_|28SxRSYe1!haNr5lfHdFS*ubPw+UfO7Fc z@*x+K37bjdiUk)|l|P2QRU~csRiC};CjUg&>CU?3p`hL-kp!k3D3FFbj%qA)B9p!L z#J_*K|Ju$4OFD@-yfeLu(pZ#Lv+Q{PuWM`aJU5;!dCo&NiAJI^d9VWwWN2|8J=yeL6Eb#u#OcAU^{Jo-)v7$e#~_# zT(M@v{H@wV_qSc&dOw>UzxQ*dyX>`mb}wS-vn!>yhCeLdJiRaOQ|0nwd6h(BNIU;h zNiy%)Dedk3s@wZV7B%f@w(~RrD12E+{oSbpOXgXQ`esP^r+2%O?PSO z059qU^t?r_znfe0G~iBGkZy5%i2|c9Kj)BXlgdGu=i^^ZY@a)9CU|i<)a#*oK(C1; zFs#?yuiN6h>K2ZIRr-Lgc~r~`P$`tF1mf2MkcaqDKec3}ja=UF@FP$Bo4L(uu{7od zv3uW78(PfQk3h#Z0=5Yx^n8U-z{cwxq_aH=_|fCuQ~SQJcKsA;ea2zLA2D@`n%5wY zvrsN!t$U+TYdI{Jp?@awv1{MZI^6oe)p85OodR$gS%o@x7uoyWiiaIAv`gMetv6SP zwT`&3VSG5J!ZZ#k@pe)-l*N03d5OWxRW*K=c&)Wn#1f-~O$wnNAS6@~e46&WzX^%Z z_WMTMECAKW^n2*dEjA}iD%M;t>E)H z%~sF25v}w_CA~d)?tnYPn)XcZ3|@^1V;$;IKO_5Lbo8UPycdofD{rAC?MWUg9FxX< z8<*_hld>D77_-7dF-_y9X!Nt8AMf37FxB}c@5hgqmxhTir>P$Af6(ahmtRU$+I2bZ zqKvV=(bL-W+K(rOSI)0c0ODQ&xQ*0bnvx#Bx2<_3dc{f0Nc9%mm(p<~>s!9(&c7V& zUAwDj#KLAX`&P8-+p&n(vw8lcSb z`jS&P@go6fN2cMX`o{S7?$`WJXSUa~Yy=0?TUNZ3M%4}W?B3;ju{Qp3g#EealN|3L z@NwC>(O2#meS-g#JfWF9b{T*CV1h!Pk3z(wK$vBHBiHb};l&alZW4ee$bLw=`7rrM zH=m2g{-jqt*M{x%RpVjqKHVMh-t)TO?(G|3sz$_WP_g6;6xhuH7ngiN8f-SI1CB2iRVg>&~6ei9Zq1{WfpSwA83^+c3Y&i+#dul9a!E7I-Bu^F0I5 z(|tF{qHjO&`7o=Ze@AtFZq}LNa1$!>bI-}1ZO%&I*zVm%Mp;K~nY#kPC3vb4w+p}& zvIv7>OpW2=!Mpkc6kJDok`-r?KI4f$;(%}qf+4K}-d+ELGrd!&G|C9f^4_w^X|$sY zp8tKocDYTURlbCz1>j4An@NvWk3XA9k9sqt(!AjFsUv~q7g-h0CnEu(q*zHri55@+ zqcvtCX89pp&tFM@J^Eg2dHgpWj<*~EvTQC1k-#bHBq-`_0ZtRChq#}Nvz^JZNMq8g zgW5SWa(eKq`{W6869|~7$bjE(a_^&?&Uw0K?$UCoQbF)W`1!N`EI=$D=zl)%+_dAa*wM(49m5x^jH zO@Ld9sg*-Po=AsM-xGRQyrGk;rUWd0DJf1Cp>^S=w!gRukx z=!43CZD$<$%X<1tX~*Kji!uE_Krb=-ydKd6Z_@&$9I%zBu4SAB^9b?J0vyZ;pcOFy zpa6w;V2r~Mm@6E~ztEpk{|^N4Am4e%Get`Dm2<&(ahm`aIRl^I(wpKxYu$Yt&#O%y zc!O#KU1AuTa~*#%?w$NbEea|H^6Y)utY?>(2S_p)b;Gf0uDmSvDji9N?(ZFK_Tr!F z<-`{SxV^}tV7-Lm~PQ$ukpD!V@daLJw$e%falps1>09of%t*|mo>BQ(4`&F=e}C^ z9b5+un72h1%mBDPUja~vi-7o?0GEgCPkJ)dbRNwC!eJhzI{CYP%jdUU8OzZC815WY z0~xA7Tq(evM*5+6;ZVFct(a$yGN#9~f*pLm3qj+>1}q-}z;QkNiUk8es{mJx)F(~- z-ZX$&0v?YpW=MaUF*MxjW^a{c=>D2N$n$5Z9I~=BUQ+wpso)jiy^>CM<}%fsG}_o7 kp2yx?Dfw{0mITlr-!rcqJ}&?lMes-dKMradz7@#*2dA5fP5=M^ delta 11020 zcmYLvbxhsQ6YagY7I*hz#i2lPcPLui-JwwQb1z=ptvD2Sm*Vd3P+UrJcV2(r_ma2S z>_0o%NzR#^%-PI?$dYy}1s0rmFcgMR9byFr{ckZB$FYCF0*ffMMXHwtk*c@Zsh0;Z z$HzIv0|D&+Mc`{x+HF_<-)PX6MJU*mod0{w)Er7_mY1O7{MAj?!o(a5^AQYb`|k!@ zqt{CGhnw5~OT=IX(*;Qw!D0oB&AKAY{(nlVq5l^_Bb6U2r7WQ$DfL-0G+EKa!;I(S z2M#Xo4;<|5l=e2JPXBUP4dqGYhe>LF7SoVZQI-r%WM>!nZ-3zV$jScS0)Z$b8o%0< zA3#C*V)IZC2=WX(4rL&LYv0$SrYboDg@5m`EB+W)x7m{)K6TM7K-3l{5ZjWuqE~;A z)9&QKlt~>YBKrg1W9nLFUKVHZR;6Lz5{m7os^t6gRNkyHMuiGnC8#Y0X7-+jTM zWsb_uh+Y9Rho@C~Fd-ADGx_u2gvB)HFyHy3!_pnxy^WPz&yZJpxmJ_p^_$-n@R1uu z(V?#wygdj+v9N^Ea?9qQ&LitBQ^FzT4;Ja`&0Qmv+%Agqy`qz7Ig?vxpYN54?shmI zv@dBuO4!CNz~uq#k%=`r0>9muDjq8}r;TmK_}8W`){AL&)0L^_0WN~_FQdN$MZ$E8 zMek*vBy}%>IF}kMaysg>E^?jaEk3X*mCD0@*S(1Ogdo604REb0!;B2#vNzei-F>!? z2jZ%+6W$g!%&1xO#qyvaynmFjA6FK=e}-_{4Eya*U>yE9Y@;4=l>43~7$>pCrs4(( zUgTq}p6D}AelAGF3IAeNttX!r6o5CQ;RYvqaJLPNPA$?^FRaj|I!H2;WjU7aKLSWa z$pZTVmMXBoNMY(nKEBbym?ehpN%n@QW5 z7yHTlG6PG50t);4__}%*J1G>7)*hOaCkm2IAck&TUE$p#FRU&6z-)3H(f?O3oYmXgm%YBPI>?v>V z)>S<=t+J&{JwCujSsKfBMGySwppwDUSSP-+HpUoZlIv}pP1#1RGQ?oT9jzjiHG$zY z0fc#|=s0I~77}2A-3X854#&doEcq6nAAO8#cZ)|etFF^=gOwG?_=TcZkQHGzzh&^L@Zk(HCu?E#V@w)Tf zIHTre@mx!F6g-a#ib2g-~f*bOe-ipI^##S7GtKV5^(Xjz1wH^-k4C8 z&bMf82ScX^&Ak1AK@LsN&Pi<{=uBt1PoaLob%Nc2$?Q5&#=?~YLe0fi&iuP!sx{IR zl$Pn2O>0~;QIbO1@fcv5<4~aUJEYeP*iIVU`dDL9izO(k?PDp-x@l%l>C2Q0kc!Dy zvwtl`yR;gb!g)`BYa)UJJYrR11I-)uLf+I`h=$4B0J;eczFI_FFY(PH7FqoHZJB-* zu4FQ2j+nk4v)sAzmtaYZC_(u%M4HaUmp5LE)FPV16Qf^4w&XRcunQo>-_`NlPWL=@ zckPt-KD@W#oHgTKPszj2WfD06s;$7vn$KmtcbqnPLdjsjN@;A5^MG#qxC3KnigziW zJiNpoKufr4q-se7kz?) zgdop@8CqWp7vVQOoY*Ke5)wj8%dQJAk7nlSZ3!T%NM%7ULf*t7eDY@qi>$H2&`=5X^6iOVy4BC2eo~dd(a?Ye zrIjPpw=`YHls3Y5vS+}Rd1gy>eBKN)^s6Urw8%#iCNqK!&FJxLNA>IZj*0J-*Ep>D z{r&#fTtmyz%+nFnxMp5qb*2Y`ZE-E?D*+4rBr6NIakOP!xCE@ul1`}kVh|`u?O!#T z0@9I|qpe|ZLHoAS*y$WtFkc=gKSKQ85i^_;GT+>UB<5SsA^_dQwX3wI34 zF}Aj6Pg~iyz$d_5`+BI8ciA4af^Q%W8mDI4kRr2F%KtQk*;m#9k5WrOUR~{~HvW*G zXH9h}g}9PrD%?*jUOLdPP`~}!=d$Q&rY*UuDq%E<&0-hix;RK?yUF^CSWMqq9m&e9 zrLnod5i{OXb{X{+l27n~zh=EAAZBX6`Xw-xI4kEVh5<;BfSV8i;bpwVhAeVE)kyu6 zyF|Ka=3>2DFRw0R<_{PFoz|DAg2+3y*Sc!`HjgSe7|KR z>p>!K<_T~}cauN6kJQ2cSjdEeEFjOrnd=@DHH|$ThLTKMi#?fgLgRKHevy@oE$ppZ zcbAtU0<1RBcyW>kMEsQyrf+}ztIy+`Dv72o8~JU?L+IhD4THFRJEmO-oUiV=iWA%Z zTo&aC4G~*y(Mcr?$+)7}j0g&MtvIgCn8an}b4~R57#cH+j4kTL%ADs*6mDb~`$1OGaemv3i#@{vH92KfP z=iL4pUYryn!mjX;j6g|L1Jw!bBW!v{Bm+kkihD=0z~5mm!Z~Z!e8%4s7$1JWj1rt7 z0+Y(;SmK{P&8GdLqVyd98-EhXcZe_o^H9f!F_?}jpHu;n47{9IAw`c~vO^fSpJVkK z^jXE{XH{WyVII%jwc0>uQm#t5^4=h60|lLoDR}qK4Xr2_&jG;dErQgHH>{)a7y%rL+FCu%qH3GNWtOhRz&jFK)+J7 z<f|mWM@AUlQ zP;ENQo;qNq*;8O3KG+9nDj@oowBi59$CD|8G^Swt<$r< zCh{!T$*qSat%HfL{U$n8+6l}GFjn00794kG>L$SuHFX#%>^d73w8pL%E$T#~-;lE4 zTGc2{8q?CP?mM{VYmAhrImP$oi(x@QQIO{zAGVOW0sGCzAO={0^iwqCe!MbwvR`{1 zxO!A(-eGtREj&9esc9X0UyWq&iJI$LiO^@JB_#@%;PstI%SAkK9QY~#Aq?!`xbv;75k2C-DG+cd`ysF zt`J~HlN)x<|JG~HBu5feD~h-Yji)Q|Xz-O2s)z3d&_ zM`Zr5y{5?*8W4w#2A7n@(456x6{d<<7+XI&Of`2J4KzIZPKTSt@&5&6QQ!{cFF#(@#i@OU5kzAAi_NQWa571kN zQR}KLLRoU)Ca7tK%22ziMRXZ$Ekgk1Lo*OvcTu7adbv6+l6t;)># z9%>eSa=GnwA2-{#SFXBo$Pjr~YhdOq`?UF_o%!JH`L3{AbT;0+%%B+q8bKpE^$P;Z$dY=_T4Eb(HDS`ikW~ z9Gowj$xeR_Qtga^lK-HVq0OUu!|37HjlVIHCB3*7E<~f#6C0Q=y%=1Q(>$jCQpY(e zS=6ZQ_mWPH_r9Hy6iwZX=1|-Pqe3+$gY4RMzt`C30f!B+U*#Fzzq(nM^xFJ2C|*7t zL>1yr48zt{1KS!8SNZ`}AkXS)ZILCDRrF4Z`rO`PYR?mf`1kh`@QbGbTdeOk@yRP^ zgyZAe-8q8x&#HA)#4gXFv6g({1E04@vA^w8(LM!hY!fPIwMGU;@tiQV9Iw!TB}`i* zsyC2vL9re%#FNL7GCQda4dD449{oddsJ$lcbd^0K%JO!wGgb~1SNj#Q?SLzZTR0Zh z_cmXw0}5*Rr|W5_&UpR$O_Kvi)X}lw2S<6Gd(?C`N$e3aS<2TO7Wm_E{)!{@jw`z%&J(%xR+bxk5Wyxe@KsvKMZEoFPQg|nN)W;&Z*sq zSIuvq(|u6Sx#)(Jebr04{9Z!!s9bnrTM#^2{FaRvw!JI=x@4CC!l+Bd9N+q-^R)Uwi+SS{b|*>)WG&H<+^oVe^%=X6ol+?8 z>8G7pI0~2V%7a@m`VVJeDN`{in45*FjvO2SwyJc|Ys}x4B?h6*kpRb0+ALfSc@)_N zHgoLkS+})ukGH4_-wroalnVhgFpL7wtZUA9`Nmu!*ope#XTInw6g2#w?z^-AF{)dX z`g?gLQc&cYxaZyy&L5M$do!fE`Aa(Q?g|7g=k}k`p-z4}&i_jEOZ6@!;9Ir}p5>DvT3rusm_?ro&oQT{sB7TvLt2DYiIf{9tlALilEt zD3JD}#irr&n5hEBC-TJwkEBBuptNCfgeIXb(p7b!U4NJi*GnOC;cTFVE6dk9)ktPI z)Gm%$Va@SJp2vKPY4HQ;?t)>wHKEh3TO^wdxgiGKAR_vf&05Lk_fPwLHO(&X@ZdMK zZH~IH67r>Ys0#XVnlp4b25PXM;Tq?wY*aQI-P|Mc9DnAE5kW!gkmm_HfUP}RN<2wcNBV{!qo3lXRX=*~&xGg8GEg|F_xbajA&LUcQ#bXQ49C=E-JmFYy;qUqi{o*IK@p&`z333|6Gb>yUVm%e(@&z?}da=i^_?w|ka@ z4DpxL3Q-J}OXTRK9aw?v2?3U>-J7>MjN~S|&8=7E9g_I{AzqjnV6i$zZz-`NUMV{X zvwdtLh{wH>E?+DP3c7$i&(*yWSo}q0riNnmzHf4K3@fkx{ki0Gw)EsVhR)`X3GFAB zsd=%RB-MByIQ9xmtF6?wKO%capQv3+7i`iZgVOB}+0>U*s;VOi+&^CEVa0cOPv|#q zq*d8FIJxYNU$2&60z0^(YZMZ>Vn;pL7K;K^>3=%WbGNI$JYcUPtvGfG6&Le;bJ3s- zo~8eOu`}QriN{fL_cq_z)wB^Zz-@apbNkUaNp;Pi2|mEZl<-+{fe#0Ts1^V zZ#ql2hEuJ77%mNuFzx-O!Hi;fm`nM+UauK5l7s)?*S8~5=TF{o*r-fU3*@5<+g@-SF1xn2IVh);>oeO4lq^TxaM6{1c+Lw9>rcZh`JX@Rd|0rpO zg5m#BGT&$--h`DYiN?2g5=Q)j@JHenePsyBo*7Z7yWIcvZ<_x1m9K?bBM4BbI2kMy zH~|exX+xAWvbj%PWYG#$C3Or}L&w%VZE>LtSb?21K=-K?m0m2}Xs8>qG5e=K=W(4S z9D#2Q{u%nCLfUl1jB>@~632b77A_1(PHUyxsS*B@ZpH*ojvsG`fnUG(*MJvuO@^hy z!LO&S65dKla&ftLz1eCH1$2EoKUbVtoif;}w-h|#XkaMC_^9M|B23e95uBQRR0oNz zo3I<*fsSv`+@GtJ1G7U>Ruc@=AiYhy z5EiY?*XwOCsrsX5LG<;sfhjz}UJ*0+@}J%?K*2%Tzsy|HhSt60`=^c9Wlj#Pnjo>t+E{7bL> zs#aQPJI%hK=D^78OA<7Fo-yW?Sh?L`fM73-zhVq zezJLofebqKd>qkf5&uek9SUZJJa;f2n!KKMRxUDqE32fO)5hXhAYGFCF*i;>@vV`76)r|6G_jQcOzn*($& zj<$sdLGn++HMj1w7HU~9>1ZrHUsx!k$Oc`J8#$F({Q5hDMqBsz)$xYmc(fXYKRh1g zXw~M{J4{CBR^T|BO7>(+s!*>KHm7v@Ab=74(3?&#KdbbkQ^CI{W(2Gw++5x7QV_z) zeao}s%gu&v$5SJ5MAme!7)LDpP@RE2)R>k&<455yDgKdO`;U8USB>bedtt@YWE?E= zEgA-E;0=ne1G~-#(x8dQpk7zx@rUnn>+2lXykhSwH=)6DngAa%bz4(McA4Mg}Mxu_Rq~Rktrw-3v8Ey_@P)sy(vj zuB!Wb>h7QD+tw#3;oANN3x=PBFbR8Mqi)XTi_t^DVvy&yB4EY^w~#OgpIs%O^ES~v z5)DbL7HnXXS!f`{?TSmLFvKvXk>;%~@fJsyKxQ(HZwNh+qjjcu?pFMM4v8LUn_vyx z{Y8j6{5b|69R5_XBJOrYFP5qE=^Y62$qK<~pg^P8aXq0~L0?^^n87*+r&IwOGy6O# zRGsD}HFSvx8wgM(^^U}JCrB=zd4n2G;4ooIWw^e{>@m>v3l!?&{_X@PQI`bnYHBrb z{BU)0DQ{Mt`ss59-9@P44d2xdhH8Hc<*9hbBfWco>ip5GM|h>agpWt5+Kn)Gnta}h zfxymPF4n0B*v^>O6?|hI&Y+A%`XQSgS1u3G_*)pZ1+bFY>ZmuVOj+}k#e})!)Fg}R3pV144Em^M{nl)* z#dq6wCtY1_&^((49T{QkiO%g1J2&vJz!;YT)sVv^u$KLtzRMF9TSTNlrR~)PVo^Lp z4ON!giBy)KFwUX29--6kqQA!qpp%|ffyWZ1cOHiITA@v~>>bi0|L~E6f^8tr)eLbg zvUp|J$M&lp2}ewlnfmC#oHFm;l}~29WD-q-NM$zCP+e~(1b!k

    Nf#W5QKTCfxy~ zd}x1>>LZP+UYyVvz6Qn9_vLZwSA0Pl(1E(~5AUXt;$f}z9%tKv)HiK8AraTD^JW94 z=38m{uVM1AoWru5oV@0wA9gFF_u+H#pGc63!lv0?J@K`+eq-4fJ54k0$Z~bv{6@^i9 z@q)d-Gw+Xwt$D=adexQ}gWmvGzGSjIXs9 zVK&A5wppwOMRVQ-;%nZi zF7ExpeZbPBMn|iy-BHRdQYGkE*L>5rLR4gPOg&!=0Sb=$=X_a-DLUQ;5BGt}?m5~; zEnnO0-@MDbJ)$@mCy8D*u(l+r-GA<3z6v@`w}J`O#3urZx`pvgL6yii>qVfa*YTgw zV7{}PO(22kOx5ZZxRx3(DOVjW_EttTvYIwEyqB-WKsh*cDohs_1+8dlSj^f^J z`E31uq23C#Vh4&Vg?BK6IbFp}lgg9Z8P~0E6#e1OzGH1idHgs{zdXupQgy<=wMo$>a7tJ#@y+m|P%w|wdInua#h%q|YuuZo}lbu<4I=_llQVDFf$!az^= z$J1=u=x_RB~Y-!+5K@9}xc&Jd90dB|ZH$8NrV07;HQCdln0z;xn* zZ9e-cCEHC1V7823b#x-rf>tA<`ikq3POTnOKm7$?+j4syty!=PN$7gytv2i2aM~%; z?NAws@Fmkk9t6k={^u^iE&mL5m>fGx^po1P<$D;B*yu2#_A3rHx3y)W5zD$x!pT>% zIucX>5Q5b1Nu_Jj&7PZINUW`s{C9v;$>w0-hgaIp zq&-9NZ>`Vum^Hn_cF1(hCwakbBCV{kTmX=Mj34GPw2>Y{&*Y6vmhWtajy zqEk$BxN`1VxB`MvQn1&*qtvym%&aV57(LaT7+9RBBo~`nOw> zf}2pr{el>TD7RP#RgF)rYbmXm!*iwZ-u?~-pg|h>dnmJtoFLG3jI!{G)Go!2ZVsrP!WNzUhfK-?>v*=X$zvqCe}XW?j^_lCi3L+ z_k6FIj(jlWoAVJ+TIhO!0nfjT*P7=|(2x;|HALM-si1h&cCStTA_U!roAezw*P!Z( zTd2nT7FjnA#IrOMHp(ay|FHf;eU(TkB$n6H2BB{H&0BWjgr%P zsfT~ET3&MCO@yUiH|}e0ZC1|&H!uk18!Dl<5u8%$&Aera6AZ^gBfV*r518wh3%8RD5A0)P05Ganl?Hmz6VVA%e9M?kLcj8 zFI{K1sC`1{iZR~Mi9TTrFz}a@WVtHM7qf+eZy?VZD!zc2>)304bNuT})!T1LXYR+< z_9eM)KBJ3R#Ztu7qW%=CIqv;zi4iraIEQl9Tt5$z6LDXs`jcIfGrG6^`HZ1s?_g@7 z*?SHBbR5i^V2$n&2;f0gv7y#$gf^?k7ab^2B)mzqtu4l;(ALGZd)>M6q}iok54@D;9EQ$x(@ zno$+@^Q0tMmb52+D&0N4h9zoQ`N54o(4s$S++w2kxoNV~0 z`59#MU!!ZcQ9tZ0VU62&Y8@Z*%uCfv*d>LT!q5o0FEwluu`VJ{yie;$6$&dTEYv%AUcT^SWUSBz{5 zGQ5g7<@d*ig)i(bZ)Nxk6FH8JNm~>1lkp#{Kc z-I1-9naiW!C^ds0ke^oN1s#J~s4bv+gs|{W2+ie}>%vN*|cLJSYS$gAw${Ta< zDpKjGP4XBWPVcTPsvG`uQu7A&Z{Xj4>Gq*j!%>9>@vG#V;J{4gf;;De&6_W|9l%GT zI(@D&WaT8}LnYP}AIc}E4 ziu0EORUDcxGu|_HsM+kRnS#U|;0e4C|Ao}+z=C2%G|@+qk#u!k5=8_=F&AdshiE=u zdZusf68;l1zXGEdN~~`)0Cw&_k)54Q9i_*b%)`OL_e4Cf*v7*@7Q(a8Sp-y{=08n| z7c3yqsh6!2Jbg)Q8t1piL!Dyxv@jW=i6x3IH}ix0JmL5v0)BTQlc46WkBWlfCNu{6 z1oHV4^CuES4kA%=<}_vrCPC)gxT+3ESN8`yp@2s^XWM3t!Sx^}AflbundLwGt*1Ir{#2 z#DIxX(G56U+1x7M+p{^E?v=e&ZbgUmH{xyDigobR?RD?m8DEx3>?wtuYAd7Sfyg?c z9=Y_j*>gbY_tIe1?1`Vp1Ijc}d$j)26)EXe{$uJu;kLB@il^h?qRzqIHN~M)H>eke zSXuP2pwP7l4oV;J3X+|Sv8Vc)Q=%@4S8tub<>2*TQ9k4_z~0nohRveKrTc{w1