From 2d96ea48f62140ffdecb6cf32a6de924dec3e50b Mon Sep 17 00:00:00 2001 From: Mai-Lapyst Date: Fri, 12 Mar 2021 23:37:00 +0100 Subject: [PATCH 01/70] 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 02/70] 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 03/70] 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 04/70] 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 05/70] 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 06/70] 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 07/70] 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 08/70] 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 785cc8c2f4d9c7aa677abb6d7949d015a7e1c2c3 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Mon, 27 Dec 2021 16:49:53 +0800 Subject: [PATCH 09/70] fix(suggest): only show readmore icon in focused item --- src/vs/editor/contrib/suggest/media/suggest.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 5c4a077dfac5f..13331bd48dd4e 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -229,7 +229,7 @@ /** Ellipsis on hover **/ -.monaco-editor .suggest-widget:not(.docs-side) .monaco-list .monaco-list-row:hover>.contents>.main>.right.can-expand-details>.details-label { +.monaco-editor .suggest-widget:not(.docs-side) .monaco-list .monaco-list-row.focused:hover>.contents>.main>.right.can-expand-details>.details-label { width: calc(100% - 26px); } @@ -284,7 +284,7 @@ display: inline-block; } -.monaco-editor .suggest-widget .monaco-list .monaco-list-row:hover>.contents>.main>.right>.readMore { +.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused:hover>.contents>.main>.right>.readMore { visibility: visible; } From 18a7108073195fc491498fe89fa27b5074cc9bd1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Dec 2021 22:38:59 +0100 Subject: [PATCH 10/70] smoke - select tab over quick pick (#139944) * smoke - further try to reduce usages of quick pick * poke --- test/automation/src/code.ts | 16 ++++++++-------- test/smoke/src/areas/workbench/data-loss.test.ts | 12 +++++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 87d2615975846..2bfffd3be84db 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -94,8 +94,8 @@ async function poll( acceptFn: (result: T) => boolean, logger: Logger, timeoutMessage: string, - retryCount: number = 200, - retryInterval: number = 100 // millis + retryCount = 200, + retryInterval = 100 // millis ): Promise { let trial = 1; let lastError: string = ''; @@ -174,8 +174,8 @@ export class Code { } } - async waitForWindowIds(fn: (windowIds: number[]) => boolean): Promise { - await poll(() => this.driver.getWindowIds(), fn, this.logger, `get window ids`); + async waitForWindowIds(accept: (windowIds: number[]) => boolean): Promise { + await poll(() => this.driver.getWindowIds(), accept, this.logger, `get window ids`); } async dispatchKeybinding(keybinding: string): Promise { @@ -262,9 +262,9 @@ export class Code { await poll(() => this.driver.isActiveElement(windowId, selector), r => r, this.logger, `is active element '${selector}'`, retryCount); } - async waitForTitle(fn: (title: string) => boolean): Promise { + async waitForTitle(accept: (title: string) => boolean): Promise { const windowId = await this.getActiveWindowId(); - await poll(() => this.driver.getTitle(windowId), fn, this.logger, `get title`); + await poll(() => this.driver.getTitle(windowId), accept, this.logger, `get title`); } async waitForTypeInEditor(selector: string, text: string): Promise { @@ -284,12 +284,12 @@ export class Code { async getLocaleInfo(): Promise { const windowId = await this.getActiveWindowId(); - return await this.driver.getLocaleInfo(windowId); + return this.driver.getLocaleInfo(windowId); } async getLocalizedStrings(): Promise { const windowId = await this.getActiveWindowId(); - return await this.driver.getLocalizedStrings(windowId); + return this.driver.getLocalizedStrings(windowId); } private async getActiveWindowId(): Promise { diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 89883f2a06236..833ab128a8340 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -95,7 +95,7 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger await app.workbench.editors.waitForTab('readme.md', !autoSave); if (typeof restartDelay === 'number') { - // this is an OK use of a timeout in a smoke test + // 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 @@ -105,10 +105,11 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger await app.restart(); await app.workbench.editors.waitForTab('readme.md', !autoSave); - await app.workbench.quickaccess.openFile(join(app.workspacePathOrFolder, 'readme.md')); + await app.workbench.editors.waitForTab('Untitled-1', true); + + await app.workbench.editors.selectTab('readme.md'); await app.workbench.editor.waitForEditorContents('readme.md', contents => contents.indexOf(textToType) > -1); - await app.workbench.editors.waitForTab('Untitled-1', true); await app.workbench.editors.selectTab('Untitled-1'); await app.workbench.editor.waitForEditorContents('Untitled-1', contents => contents.indexOf(textToTypeInUntitled) > -1); @@ -230,10 +231,11 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger await insidersApp.start(); await insidersApp.workbench.editors.waitForTab('readme.md', true); - await insidersApp.workbench.quickaccess.openFile(join(insidersApp.workspacePathOrFolder, 'readme.md')); + await insidersApp.workbench.editors.waitForTab('Untitled-1', true); + + await insidersApp.workbench.editors.selectTab('readme.md'); await insidersApp.workbench.editor.waitForEditorContents('readme.md', contents => contents.indexOf(textToType) > -1); - await insidersApp.workbench.editors.waitForTab('Untitled-1', true); await insidersApp.workbench.editors.selectTab('Untitled-1'); await insidersApp.workbench.editor.waitForEditorContents('Untitled-1', contents => contents.indexOf(textToTypeInUntitled) > -1); From 8d7406731bc3634dd33973f7a277bc6af321a0e1 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Dec 2021 12:31:08 -0800 Subject: [PATCH 11/70] substr -> substring in debug --- src/vs/workbench/contrib/debug/browser/baseDebugView.ts | 2 +- src/vs/workbench/contrib/debug/browser/callStackView.ts | 2 +- .../contrib/debug/browser/debugEditorContribution.ts | 2 +- .../contrib/debug/browser/extensionHostDebugService.ts | 2 +- src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts | 2 +- src/vs/workbench/contrib/debug/browser/repl.ts | 6 +++--- src/vs/workbench/contrib/debug/common/replModel.ts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 295056cc357c9..91e9625030dbc 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -78,7 +78,7 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer | } if (options.maxValueLength && value && value.length > options.maxValueLength) { - value = value.substr(0, options.maxValueLength) + '...'; + value = value.substring(0, options.maxValueLength) + '...'; } if (!value) { value = ''; diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index a1e860f833660..675ea6f2c06df 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -100,7 +100,7 @@ export function getSpecificSourceName(stackFrame: IStackFrame): string { } const from = Math.max(0, stackFrame.source.uri.path.lastIndexOf(posix.sep, stackFrame.source.uri.path.length - suffixLength - 1)); - return (from > 0 ? '...' : '') + stackFrame.source.uri.path.substr(from); + return (from > 0 ? '...' : '') + stackFrame.source.uri.path.substring(from); } async function expandTo(session: IDebugSession, tree: WorkbenchCompressibleAsyncDataTree): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 5941c3e098800..7285a69f44bb1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -71,7 +71,7 @@ class InlineSegment { function createInlineValueDecoration(lineNumber: number, contentText: string, column = Constants.MAX_SAFE_SMALL_INTEGER): IModelDeltaDecoration { // If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) { - contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...'; + contentText = contentText.substring(0, MAX_INLINE_DECORATOR_LENGTH) + '...'; } return { diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 57fcbfb82c2be..ec366aff788da 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -165,7 +165,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i for (const a of args) { const k = `--${key}=`; if (a.indexOf(k) === 0) { - return a.substr(k.length); + return a.substring(k.length); } } diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 6787dfbc5551c..cdc52b970cab4 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -339,7 +339,7 @@ class SessionTreeItem extends BaseTreeItem { folder = this.rootProvider ? this.rootProvider.getWorkspaceFolder(resource) : null; if (folder) { // strip off the root folder path - path = normalize(ltrim(resource.path.substr(folder.uri.path.length), posix.sep)); + path = normalize(ltrim(resource.path.substring(folder.uri.path.length), posix.sep)); const hasMultipleRoots = this.rootProvider.getWorkspace().folders.length > 1; if (hasMultipleRoots) { path = posix.sep + path; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 2f261329786a6..ca1893076ee07 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -245,15 +245,15 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { // If a debug completion item sets a selection we need to use snippets to make sure the selection is selected #90974 insertTextRules = CompletionItemInsertTextRule.InsertAsSnippet; const selectionLength = typeof item.selectionLength === 'number' ? item.selectionLength : 0; - const placeholder = selectionLength > 0 ? '${1:' + insertText.substr(item.selectionStart, selectionLength) + '}$0' : '$0'; - insertText = insertText.substr(0, item.selectionStart) + placeholder + insertText.substr(item.selectionStart + selectionLength); + const placeholder = selectionLength > 0 ? '${1:' + insertText.substring(item.selectionStart, item.selectionStart + selectionLength) + '}$0' : '$0'; + insertText = insertText.substring(0, item.selectionStart) + placeholder + insertText.substring(item.selectionStart + selectionLength); } suggestions.push({ label: item.label, insertText, kind: CompletionItemKinds.fromString(item.type || 'property'), - filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, + filterText: (item.start && item.length) ? text.substring(item.start, item.start + item.length).concat(item.label) : undefined, range: computeRange(item.length || overwriteBefore), sortText: item.sortText, insertTextRules diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 51730165a7372..13ad89354d175 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -233,7 +233,7 @@ export class ReplModel { // [2J is the ansi escape sequence for clearing the display http://ascii-table.com/ansi-escape-sequences.php this.removeReplExpressions(); this.appendToRepl(session, nls.localize('consoleCleared', "Console was cleared"), severity.Ignore); - data = data.substr(data.lastIndexOf(clearAnsiSequence) + clearAnsiSequence.length); + data = data.substring(data.lastIndexOf(clearAnsiSequence) + clearAnsiSequence.length); } if (typeof data === 'string') { From 2cf8ae8a1394e7428c840000a48c621f986a4660 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Dec 2021 12:58:06 -0800 Subject: [PATCH 12/70] prefer-const in debug --- .../contrib/debug/browser/callStackView.ts | 2 +- .../contrib/debug/browser/debugANSIHandling.ts | 5 ++--- .../contrib/debug/browser/debugActionViewItems.ts | 2 +- .../contrib/debug/browser/debugAdapterManager.ts | 8 ++++---- .../contrib/debug/browser/debugCommands.ts | 6 +++--- .../debug/browser/debugConfigurationManager.ts | 8 ++++---- .../debug/browser/debugEditorContribution.ts | 10 +++++----- .../contrib/debug/browser/debugQuickAccess.ts | 2 +- .../contrib/debug/browser/debugToolBar.ts | 2 +- .../contrib/debug/browser/disassemblyView.ts | 4 ++-- .../contrib/debug/browser/exceptionWidget.ts | 4 ++-- .../contrib/debug/browser/loadedScriptsView.ts | 4 ++-- .../contrib/debug/browser/rawDebugSession.ts | 2 +- src/vs/workbench/contrib/debug/browser/repl.ts | 2 +- .../workbench/contrib/debug/browser/replFilter.ts | 2 +- .../workbench/contrib/debug/browser/replViewer.ts | 4 ++-- .../workbench/contrib/debug/common/debugModel.ts | 5 ++--- .../workbench/contrib/debug/common/debugSource.ts | 2 +- .../workbench/contrib/debug/common/debugUtils.ts | 10 +++++----- src/vs/workbench/contrib/debug/common/replModel.ts | 2 +- .../workbench/contrib/debug/node/debugAdapter.ts | 4 ++-- src/vs/workbench/contrib/debug/node/terminals.ts | 12 ++++++------ .../debug/test/browser/baseDebugView.test.ts | 2 +- .../contrib/debug/test/browser/callStack.test.ts | 6 ++---- .../debug/test/browser/debugANSIHandling.test.ts | 8 ++++---- .../contrib/debug/test/browser/debugHover.test.ts | 12 ++++-------- .../contrib/debug/test/browser/debugSource.test.ts | 2 +- .../contrib/debug/test/browser/repl.test.ts | 14 +++++++------- 28 files changed, 69 insertions(+), 77 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 675ea6f2c06df..456c31d3b7fe0 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -190,7 +190,7 @@ export class CallStackView extends ViewPane { toExpand.add(s.parentSession); } }); - for (let session of toExpand) { + for (const session of toExpand) { await expandTo(session, this.tree); this.autoExpandedSessions.add(session); } diff --git a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts index e5df145ed57ed..131dd54bbc4ec 100644 --- a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts @@ -135,8 +135,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * [] flag to make sure it is appropriate to turn ON or OFF (if it is already inverted don't call */ function reverseForegroundAndBackgroundColors(): void { - let oldFgColor: RGBA | undefined; - oldFgColor = customFgColor; + const oldFgColor: RGBA | undefined = customFgColor; changeColor('foreground', customBgColor); changeColor('background', oldFgColor); } @@ -155,7 +154,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#SGR } */ function setBasicFormatters(styleCodes: number[]): void { - for (let code of styleCodes) { + for (const code of styleCodes) { switch (code) { case 0: { // reset (everything) styleNames = []; diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index b68d7d7036ccd..167e3114c04f8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -74,7 +74,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { container.classList.add('start-debug-action-item'); this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart))); const keybinding = this.keybindingService.lookupKeybinding(this.action.id)?.getLabel(); - let keybindingLabel = keybinding ? ` (${keybinding})` : ''; + const keybindingLabel = keybinding ? ` (${keybinding})` : ''; this.start.title = this.action.label + keybindingLabel; this.start.setAttribute('role', 'button'); diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 9d648d0568b48..9111e61569272 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -193,7 +193,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { } hasEnabledDebuggers(): boolean { - for (let [type] of this.debugAdapterFactories) { + for (const [type] of this.debugAdapterFactories) { const dbg = this.getDebugger(type); if (dbg && dbg.enabled) { return true; @@ -204,7 +204,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { } createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined { - let factory = this.debugAdapterFactories.get(session.configuration.type); + const factory = this.debugAdapterFactories.get(session.configuration.type); if (factory) { return factory.createDebugAdapter(session); } @@ -212,7 +212,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { } substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise { - let factory = this.debugAdapterFactories.get(debugType); + const factory = this.debugAdapterFactories.get(debugType); if (factory) { return factory.substituteVariables(folder, config); } @@ -220,7 +220,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { } runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise { - let factory = this.debugAdapterFactories.get(debugType); + const factory = this.debugAdapterFactories.get(debugType); if (factory) { return factory.runInTerminal(args, sessionId); } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 81c4a0817a324..8478a0b5c4674 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -144,7 +144,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { const textResourcePropertiesService = accessor.get(ITextResourcePropertiesService); const clipboardService = accessor.get(IClipboardService); - let frame = getFrame(accessor.get(IDebugService), context); + const frame = getFrame(accessor.get(IDebugService), context); if (frame) { const eol = textResourcePropertiesService.getEOL(frame.source.uri); await clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); @@ -353,7 +353,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); const notificationService = accessor.get(INotificationService); - let frame = getFrame(debugService, context); + const frame = getFrame(debugService, context); if (frame) { try { await frame.restart(); @@ -423,7 +423,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: async (accessor: ServicesAccessor, debugStartOptions?: { config?: Partial; noDebug?: boolean }) => { const debugService = accessor.get(IDebugService); await saveAllBeforeDebugStart(accessor.get(IConfigurationService), accessor.get(IEditorService)); - let { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration; + const { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration; const config = await getConfig(); const configOrName = config ? Object.assign(deepClone(config), debugStartOptions?.config) : name; await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true }, false); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 36bc520d4a5b9..951b024a5ef03 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -264,8 +264,8 @@ export class ConfigurationManager implements IConfigurationManager { getAllConfigurations(): { launch: ILaunch; name: string; presentation?: IConfigPresentation }[] { const all: { launch: ILaunch, name: string, presentation?: IConfigPresentation }[] = []; - for (let l of this.launches) { - for (let name of l.getConfigurationNames()) { + for (const l of this.launches) { + for (const name of l.getConfigurationNames()) { const config = l.getConfiguration(name) || l.getCompound(name); if (config) { all.push({ launch: l, name, presentation: config.presentation }); @@ -660,10 +660,10 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { } async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> { - let launchExistInFile = !!this.getConfig(); + const launchExistInFile = !!this.getConfig(); if (!launchExistInFile) { // Launch property in workspace config not found: create one by collecting launch configs from debugConfigProviders - let content = await this.getInitialConfigurationContent(undefined, type, token); + const content = await this.getInitialConfigurationContent(undefined, type, token); if (content) { await this.configurationService.updateValue('launch', json.parse(content), ConfigurationTarget.WORKSPACE); } else { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 7285a69f44bb1..2b42e4bebb54d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -99,7 +99,7 @@ function replaceWsWithNoBreakWs(str: string): string { function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray, range: Range, model: ITextModel, wordToLineNumbersMap: Map): IModelDeltaDecoration[] { const nameValueMap = new Map(); - for (let expr of expressions) { + for (const expr of expressions) { nameValueMap.set(expr.name, expr.value); // Limit the size of map. Too large can have a perf impact if (nameValueMap.size >= MAX_NUM_INLINE_VALUES) { @@ -113,7 +113,7 @@ function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray { const lineNumbers = wordToLineNumbersMap.get(name); if (lineNumbers) { - for (let lineNumber of lineNumbers) { + for (const lineNumber of lineNumbers) { if (range.containsPosition(new Position(lineNumber, 0))) { if (!lineToNamesMap.has(lineNumber)) { lineToNamesMap.set(lineNumber, []); @@ -324,7 +324,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private enableEditorHover(): void { if (this.editor.hasModel()) { const model = this.editor.getModel(); - let overrides = { + const overrides = { resource: model.uri, overrideIdentifier: model.getLanguageId() }; @@ -614,7 +614,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { const findVariable = async (_key: string, caseSensitiveLookup: boolean): Promise => { const scopes = await stackFrame.getMostSpecificScopes(stackFrame.range); const key = caseSensitiveLookup ? _key : _key.toLowerCase(); - for (let scope of scopes) { + for (const scope of scopes) { const variables = await scope.getChildren(); const found = variables.find(v => caseSensitiveLookup ? (v.name === key) : (v.name.toLowerCase() === key)); if (found) { @@ -638,7 +638,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { const promises = flatten(providers.map(provider => ranges.map(range => Promise.resolve(provider.provideInlineValues(model, range, ctx, token)).then(async (result) => { if (result) { - for (let iv of result) { + for (const iv of result) { let text: string | undefined = undefined; switch (iv.type) { diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 8750a4769e049..06e1449c8f23c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -45,7 +45,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { group: 'navigation', diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index a44bde2c4b4ba..4681e8b3da357 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -617,7 +617,7 @@ class InstructionRenderer extends Disposable implements ITableRenderer this.compare(a, b)); @@ -185,7 +185,7 @@ class BaseTreeItem { if (this._source && this._parent && this._parent._source) { return this._source.raw.path || this._source.raw.name; } - let label = this.getLabel(false); + const label = this.getLabel(false); const parent = this.getParent(); if (parent) { const hover = parent.getHoverLabel(); diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 24ffbc48e10da..0aa9f6bbe9cdf 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -641,7 +641,7 @@ export class RawDebugSession implements IDisposable { const args: string[] = []; - for (let arg of vscodeArgs.args) { + for (const arg of vscodeArgs.args) { const a2 = (arg.prefix || '') + (arg.path || ''); const match = /^--(.+)=(.+)$/.exec(a2); if (match && match.length === 3) { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index ca1893076ee07..ae412a0a9868a 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -528,7 +528,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (session) { // Automatically expand repl group elements when specified const autoExpandElements = async (elements: IReplElement[]) => { - for (let element of elements) { + for (const element of elements) { if (element instanceof ReplGroup) { if (element.autoExpand && !autoExpanded.has(element.getId())) { autoExpanded.add(element.getId()); diff --git a/src/vs/workbench/contrib/debug/browser/replFilter.ts b/src/vs/workbench/contrib/debug/browser/replFilter.ts index c724392cb12f5..8a901cf9f008f 100644 --- a/src/vs/workbench/contrib/debug/browser/replFilter.ts +++ b/src/vs/workbench/contrib/debug/browser/replFilter.ts @@ -64,7 +64,7 @@ export class ReplFilter implements ITreeFilter { const text = element.toString(true); - for (let { type, query } of this._parsedQueries) { + for (const { type, query } of this._parsedQueries) { if (type === 'exclude' && ReplFilter.matchQuery(query, text)) { // If exclude query matches, ignore all other queries and hide return false; diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 2dce692d1691a..64b12af4e871f 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -313,8 +313,8 @@ export class ReplDelegate extends CachedListVirtualDelegate { // Calculate a rough overestimation for the height // For every 70 characters increase the number of lines needed beyond the first if (hasValue(element) && !(element instanceof Variable)) { - let value = element.value; - let valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 70)); + const value = element.value; + const valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 70)); return valueRows * rowHeight; } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 92018dd69a0ae..d9da5ab01bec0 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -266,13 +266,12 @@ export class Variable extends ExpressionContainer implements IExpression { } try { - let response: DebugProtocol.SetExpressionResponse | DebugProtocol.SetVariableResponse | undefined; // Send out a setExpression for debug extensions that do not support set variables https://github.com/microsoft/vscode/issues/124679#issuecomment-869844437 if (this.session.capabilities.supportsSetExpression && !this.session.capabilities.supportsSetVariable && this.evaluateName) { return this.setExpression(value, stackFrame); } - response = await this.session.setVariable((this.parent).reference, this.name, value); + const response = await this.session.setVariable((this.parent).reference, this.name, value); handleSetResponse(this, response); } catch (err) { this.errorMessage = err.message; @@ -1081,7 +1080,7 @@ export class DebugModel implements IDebugModel { } rawUpdate(data: IRawModelUpdate): void { - let session = this.sessions.find(p => p.getId() === data.sessionId); + const session = this.sessions.find(p => p.getId() === data.sessionId); if (session) { session.rawUpdate(data); this._onDidChangeCallStack.fire(undefined); diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index 3ecc861082127..d7b43b14248f9 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -99,7 +99,7 @@ export class Source { path = modelUri.path; if (modelUri.query) { const keyvalues = modelUri.query.split('&'); - for (let keyvalue of keyvalues) { + for (const keyvalue of keyvalues) { const pair = keyvalue.split('='); if (pair.length === 2) { switch (pair[0]) { diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 3811dc17bf056..f9a59f1cba7cd 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -78,13 +78,13 @@ export function getExactExpressionStartAndEnd(lineContent: string, looseStart: n // Some example supported expressions: myVar.prop, a.b.c.d, myVar?.prop, myVar->prop, MyClass::StaticProp, *myVar // Match any character except a set of characters which often break interesting sub-expressions - let expression: RegExp = /([^()\[\]{}<>\s+\-/%~#^;=|,`!]|\->)+/g; + const expression: RegExp = /([^()\[\]{}<>\s+\-/%~#^;=|,`!]|\->)+/g; let result: RegExpExecArray | null = null; // First find the full expression under the cursor while (result = expression.exec(lineContent)) { - let start = result.index + 1; - let end = start + result[0].length; + const start = result.index + 1; + const end = start + result[0].length; if (start <= looseStart && end >= looseEnd) { matchingExpression = result[0]; @@ -96,10 +96,10 @@ export function getExactExpressionStartAndEnd(lineContent: string, looseStart: n // If there are non-word characters after the cursor, we want to truncate the expression then. // For example in expression 'a.b.c.d', if the focus was under 'b', 'a.b' would be evaluated. if (matchingExpression) { - let subExpression: RegExp = /\w+/g; + const subExpression: RegExp = /\w+/g; let subExpressionResult: RegExpExecArray | null = null; while (subExpressionResult = subExpression.exec(matchingExpression)) { - let subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length; + const subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length; if (subEnd >= looseEnd) { break; } diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 13ad89354d175..59b388ccabff7 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -306,7 +306,7 @@ export class ReplModel { // add output for each argument logged let simpleVals: any[] = []; for (let i = 0; i < args.length; i++) { - let a = args[i]; + const a = args[i]; // undefined gets printed as 'undefined' if (typeof a === 'undefined') { diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index bfef068fa257e..6347d7ba2a5e8 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -382,9 +382,9 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { platformInfo = platformInfo || result; // these are the relevant attributes - let program = platformInfo.program || result.program; + const program = platformInfo.program || result.program; const args = platformInfo.args || result.args; - let runtime = platformInfo.runtime || result.runtime; + const runtime = platformInfo.runtime || result.runtime; const runtimeArgs = platformInfo.runtimeArgs || result.runtimeArgs; if (runtime) { diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 74eb2c16242fb..4950fb6314843 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -121,7 +121,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? command += `cd ${quote(cwd)}; `; } if (env) { - for (let key in env) { + for (const key in env) { const value = env[key]; if (value === null) { command += `Remove-Item env:${key}; `; @@ -133,7 +133,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? if (args.length > 0) { const cmd = quote(args.shift()!); command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `; - for (let a of args) { + for (const a of args) { command += `${quote(a)} `; } } @@ -155,7 +155,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? } if (env) { command += 'cmd /C "'; - for (let key in env) { + for (const key in env) { let value = env[key]; if (value === null) { command += `set "${key}=" && `; @@ -165,7 +165,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? } } } - for (let a of args) { + for (const a of args) { command += `${quote(a)} `; } if (env) { @@ -189,7 +189,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? } if (env) { command += '/usr/bin/env'; - for (let key in env) { + for (const key in env) { const value = env[key]; if (value === null) { command += ` -u ${hardQuote(key)}`; @@ -199,7 +199,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? } command += ' '; } - for (let a of args) { + for (const a of args) { command += `${quote(a)} `; } break; diff --git a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index 4ddd3bbb80c24..c498cffe2caf0 100644 --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -94,7 +94,7 @@ suite('Debug - Base Debug View', () => { let expression = $('.'); let name = $('.'); let value = $('.'); - let label = new HighlightedLabel(name); + const label = new HighlightedLabel(name); renderVariable(variable, { expression, name, value, label }, false, []); assert.strictEqual(label.element.textContent, 'foo'); diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 64a908055232b..13dfdda095545 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -42,8 +42,6 @@ export function createMockSession(model: DebugModel, name = 'mockSession', optio } function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } { - let firstStackFrame: StackFrame; - let secondStackFrame: StackFrame; const thread = new class extends Thread { public override getCallStack(): StackFrame[] { return [firstStackFrame, secondStackFrame]; @@ -61,8 +59,8 @@ function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFr sourceReference: 11, }, 'aDebugSessionId', mockUriIdentityService); - firstStackFrame = new StackFrame(thread, 0, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 10 }, 0, true); - secondStackFrame = new StackFrame(thread, 1, secondSource, 'app2.js', 'normal', { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 10 }, 1, true); + const firstStackFrame = new StackFrame(thread, 0, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 10 }, 0, true); + const secondStackFrame = new StackFrame(thread, 1, secondSource, 'app2.js', 'normal', { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 10 }, 1, true); return { firstStackFrame, secondStackFrame }; } diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index 8c1c0f275db4f..b3a85c5625904 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -39,7 +39,7 @@ suite('Debug - ANSI Handling', () => { linkDetector = instantiationService.createInstance(LinkDetector); const colors: { [id: string]: string; } = {}; - for (let color in ansiColorMap) { + for (const color in ansiColorMap) { colors[color] = ansiColorMap[color].defaults.dark; } const testTheme = new TestColorTheme(colors); @@ -347,7 +347,7 @@ suite('Debug - ANSI Handling', () => { for (let r = 0; r <= 255; r += 64) { for (let g = 0; g <= 255; g += 64) { for (let b = 0; b <= 255; b += 64) { - let color = new RGBA(r, g, b); + const color = new RGBA(r, g, b); // Foreground codes should add class and inline style assertSingleSequenceElement(`\x1b[38;2;${r};${g};${b}m`, (child) => { assert(child.classList.contains('code-foreground-colored'), 'DOM should have "code-foreground-colored" class for advanced ANSI colors.'); @@ -1033,7 +1033,7 @@ suite('Debug - ANSI Handling', () => { for (let red = 0; red <= 5; red++) { for (let green = 0; green <= 5; green++) { for (let blue = 0; blue <= 5; blue++) { - let colorOut: any = calcANSI8bitColor(16 + red * 36 + green * 6 + blue); + const colorOut: any = calcANSI8bitColor(16 + red * 36 + green * 6 + blue); assert(colorOut.r === Math.round(red * (255 / 5)), 'Incorrect red value encountered for color'); assert(colorOut.g === Math.round(green * (255 / 5)), 'Incorrect green value encountered for color'); assert(colorOut.b === Math.round(blue * (255 / 5)), 'Incorrect balue value encountered for color'); @@ -1043,7 +1043,7 @@ suite('Debug - ANSI Handling', () => { // All grays for (let i = 232; i <= 255; i++) { - let grayOut: any = calcANSI8bitColor(i); + const grayOut: any = calcANSI8bitColor(i); assert(grayOut.r === grayOut.g); assert(grayOut.r === grayOut.b); assert(grayOut.r === Math.round((i - 232) / 23 * 255)); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts index 19b009efad283..89aa91e164c28 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts @@ -15,7 +15,6 @@ suite('Debug - Hover', () => { test('find expression in stack frame', async () => { const model = createMockDebugModel(); const session = createMockSession(model); - let stackFrame: StackFrame; const thread = new class extends Thread { public override getCallStack(): StackFrame[] { @@ -29,28 +28,25 @@ suite('Debug - Hover', () => { sourceReference: 10, }, 'aDebugSessionId', mockUriIdentityService); - let scope: Scope; - stackFrame = new class extends StackFrame { + const stackFrame = new class extends StackFrame { override getScopes(): Promise { return Promise.resolve([scope]); } }(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1, true); - let variableA: Variable; - let variableB: Variable; - scope = new class extends Scope { + const scope = new class extends Scope { override getChildren(): Promise { return Promise.resolve([variableA]); } }(stackFrame, 1, 'local', 1, false, 10, 10); - variableA = new class extends Variable { + const variableA = new class extends Variable { override getChildren(): Promise { return Promise.resolve([variableB]); } }(session, 1, scope, 2, 'A', 'A', undefined!, 0, 0, {}, 'string'); - variableB = new Variable(session, 1, scope, 2, 'B', 'A.B', undefined!, 0, 0, {}, 'string'); + const variableB = new Variable(session, 1, scope, 2, 'B', 'A.B', undefined!, 0, 0, {}, 'string'); assert.strictEqual(await findExpressionInStackFrame(stackFrame, []), undefined); assert.strictEqual(await findExpressionInStackFrame(stackFrame, ['A']), variableA); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts index 9151891fcf569..a041118a1e45d 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts @@ -42,7 +42,7 @@ suite('Debug - Source', () => { test('get encoded debug data', () => { const checkData = (uri: uri, expectedName: string, expectedPath: string, expectedSourceReference: number | undefined, expectedSessionId?: string) => { - let { name, path, sourceReference, sessionId } = Source.getEncodedDebugData(uri); + const { name, path, sourceReference, sessionId } = Source.getEncodedDebugData(uri); assert.strictEqual(name, expectedName); assert.strictEqual(path, expectedPath); assert.strictEqual(sourceReference, expectedSourceReference); diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index 50567b6a93fa7..d9b6520098cae 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -250,19 +250,19 @@ suite('Debug - REPL', () => { repl.appendToRepl(session, 'fourth line\n', severity.Info); replFilter.filterQuery = 'first'; - let r1 = getFilteredElements(); + const r1 = getFilteredElements(); assert.strictEqual(r1.length, 1); assert.strictEqual(r1[0].value, 'first line\n'); replFilter.filterQuery = '!first'; - let r2 = getFilteredElements(); + const r2 = getFilteredElements(); assert.strictEqual(r1.length, 1); assert.strictEqual(r2[0].value, 'second line\n'); assert.strictEqual(r2[1].value, 'third line\n'); assert.strictEqual(r2[2].value, 'fourth line\n'); replFilter.filterQuery = 'first, line'; - let r3 = getFilteredElements(); + const r3 = getFilteredElements(); assert.strictEqual(r3.length, 4); assert.strictEqual(r3[0].value, 'first line\n'); assert.strictEqual(r3[1].value, 'second line\n'); @@ -270,22 +270,22 @@ suite('Debug - REPL', () => { assert.strictEqual(r3[3].value, 'fourth line\n'); replFilter.filterQuery = 'line, !second'; - let r4 = getFilteredElements(); + const r4 = getFilteredElements(); assert.strictEqual(r4.length, 3); assert.strictEqual(r4[0].value, 'first line\n'); assert.strictEqual(r4[1].value, 'third line\n'); assert.strictEqual(r4[2].value, 'fourth line\n'); replFilter.filterQuery = '!second, line'; - let r4_same = getFilteredElements(); + const r4_same = getFilteredElements(); assert.strictEqual(r4.length, r4_same.length); replFilter.filterQuery = '!line'; - let r5 = getFilteredElements(); + const r5 = getFilteredElements(); assert.strictEqual(r5.length, 0); replFilter.filterQuery = 'smth'; - let r6 = getFilteredElements(); + const r6 = getFilteredElements(); assert.strictEqual(r6.length, 0); }); }); From a2655c2e5f21de6e37761304f8954b60445da82d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Dec 2021 13:10:02 -0800 Subject: [PATCH 13/70] prefer-const in notebooks --- .../src/singlefolder-tests/notebook.test.ts | 2 +- .../api/common/extHostNotebookKernels.ts | 6 ++-- .../breakpoints/notebookBreakpoints.ts | 2 +- .../contrib/cellCommands/cellCommands.ts | 2 +- .../contributedStatusBarItemController.ts | 4 +-- .../executionStatusBarItemController.ts | 4 +-- .../editorStatusBar/editorStatusBar.ts | 6 ++-- .../browser/contrib/find/findController.ts | 4 +-- .../notebook/browser/contrib/fold/folding.ts | 8 +++--- .../browser/contrib/fold/foldingModel.ts | 16 +++++------ .../browser/contrib/layout/layoutActions.ts | 6 ++-- .../contrib/outline/notebookOutline.ts | 28 +++++++++---------- .../contrib/profile/notebookProfile.ts | 2 +- .../viewportCustomMarkdown.ts | 2 +- .../browser/controller/cellOperations.ts | 8 +++--- .../browser/controller/editActions.ts | 2 +- .../notebook/browser/diff/diffComponents.ts | 8 +++--- .../browser/diff/diffElementViewModel.ts | 4 +-- .../notebook/browser/notebook.contribution.ts | 4 +-- .../notebook/browser/notebookBrowser.ts | 2 +- .../notebook/browser/notebookEditorWidget.ts | 2 +- .../notebook/browser/notebookServiceImpl.ts | 4 +-- .../view/cellParts/cellEditorOptions.ts | 4 +-- .../browser/view/cellParts/cellStatusPart.ts | 2 +- .../notebook/browser/view/notebookCellList.ts | 2 +- .../browser/view/renderers/webviewPreloads.ts | 2 +- .../browser/viewModel/codeCellViewModel.ts | 2 +- .../browser/viewModel/notebookViewModel.ts | 2 +- .../viewParts/notebookEditorDecorations.ts | 2 +- .../viewParts/notebookEditorToolbar.ts | 10 +++---- .../common/model/notebookCellTextModel.ts | 4 +-- .../common/model/notebookTextModel.ts | 12 ++++---- .../contrib/notebook/common/notebookCommon.ts | 4 +-- .../notebook/common/notebookEditorModel.ts | 2 +- .../notebookEditorModelResolverServiceImpl.ts | 2 +- .../notebook/common/notebookPerformance.ts | 2 +- .../notebook/common/notebookProvider.ts | 6 ++-- .../services/notebookWorkerServiceImpl.ts | 8 +++--- 38 files changed, 96 insertions(+), 96 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 b5e72d11cc564..0997367b60a30 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -50,7 +50,7 @@ class Kernel { } protected async _execute(cells: vscode.NotebookCell[]): Promise { - for (let cell of cells) { + for (const cell of cells) { await this._runCell(cell); } } diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 923f5cc532d6b..0205f542e1c20 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -87,7 +87,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { createNotebookController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler?: (cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) => void | Thenable, preloads?: vscode.NotebookRendererScript[]): vscode.NotebookController { - for (let data of this._kernelData.values()) { + for (const data of this._kernelData.values()) { if (data.controller.id === id && ExtensionIdentifier.equals(extension.identifier, data.extensionId)) { throw new Error(`notebook controller with id '${id}' ALREADY exist`); } @@ -284,7 +284,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { } const document = this._extHostNotebook.getNotebookDocument(URI.revive(uri)); const cells: vscode.NotebookCell[] = []; - for (let cellHandle of handles) { + for (const cellHandle of handles) { const cell = document.getCell(cellHandle); if (cell) { cells.push(cell.apiCell); @@ -315,7 +315,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { await obj.controller.interruptHandler.call(obj.controller, document.apiNotebook); } else { - for (let cellHandle of handles) { + for (const cellHandle of handles) { const cell = document.getCell(cellHandle); if (cell) { this._activeExecutions.get(cell.uri)?.cancel(); 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 4610dde5ac23b..570d2dd93f85a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints.ts @@ -41,7 +41,7 @@ class NotebookBreakpoints extends Disposable implements IWorkbenchContribution { return; } - for (let change of e.rawEvent.changes) { + for (const change of e.rawEvent.changes) { const [start, deleteCount] = change; if (deleteCount > 0) { const deleted = model.cells.slice(start, start + deleteCount); 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 03941006678f0..cdf88189cf524 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -470,7 +470,7 @@ registerAction2(class extends NotebookMultiCellAction { cells = context.selectedCells; } - for (let cell of cells) { + for (const cell of cells) { cell.isOutputCollapsed = !cell.isOutputCollapsed; } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts index a3d97ab1971f4..82289649e0cd4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts @@ -54,12 +54,12 @@ export class ContributedStatusBarItemController extends Disposable implements IN return; } - for (let newCell of e.added) { + for (const newCell of e.added) { const helper = new CellStatusBarHelper(vm, newCell, this._notebookCellStatusBarService); this._visibleCells.set(newCell.handle, helper); } - for (let oldCell of e.removed) { + for (const oldCell of e.removed) { this._visibleCells.get(oldCell.handle)?.dispose(); this._visibleCells.delete(oldCell.handle); } 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 b00cd32a5c476..a57604e0b69d5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts @@ -44,11 +44,11 @@ export class NotebookStatusBarController extends Disposable { return; } - for (let newCell of e.added) { + for (const newCell of e.added) { this._visibleCells.set(newCell.handle, this._itemFactory(vm, newCell)); } - for (let oldCell of e.removed) { + for (const oldCell of e.removed) { this._visibleCells.get(oldCell.handle)?.dispose(); this._visibleCells.delete(oldCell.handle); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 5200c33296975..7b9574403d7ea 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -139,7 +139,7 @@ registerAction2(class extends Action2 { let newKernel: INotebookKernel | undefined; if (controllerId) { const wantedId = `${extensionId}/${controllerId}`; - for (let candidate of all) { + for (const candidate of all) { if (candidate.id === wantedId) { newKernel = candidate; break; @@ -267,7 +267,7 @@ class ImplictKernelSelector implements IDisposable { // IMPLICITLY select a suggested kernel when the notebook has been changed // e.g change cell source, move cells, etc disposables.add(notebook.onDidChangeContent(e => { - for (let event of e.rawEvents) { + for (const event of e.rawEvents) { switch (event.kind) { case NotebookCellsChangeType.ChangeCellContent: case NotebookCellsChangeType.ModelChange: @@ -346,7 +346,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { this._kernelInfoElement.clear(); - let { selected, suggestions, all } = this._notebookKernelService.getMatchingKernel(notebook); + const { selected, suggestions, all } = this._notebookKernelService.getMatchingKernel(notebook); const suggested = (suggestions.length === 1 && all.length === 1) ? suggestions[0] : undefined; let isSuggested = false; 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 b1001be86c884..a3bbfbe7cfab9 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -136,7 +136,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote const replacePattern = this.replacePattern; const cellFindMatches = this._findModel.findMatches; - let replaceStrings: string[] = []; + const replaceStrings: string[] = []; cellFindMatches.forEach(cellFindMatch => { const findMatches = cellFindMatch.matches; findMatches.forEach(findMatch => { @@ -271,7 +271,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote if (this._state.matchesCount >= MATCHES_LIMIT) { matchesCount += '+'; } - let matchesPosition: string = this._findModel.currentMatch < 0 ? '?' : String((this._findModel.currentMatch + 1)); + const matchesPosition: string = this._findModel.currentMatch < 0 ? '?' : String((this._findModel.currentMatch + 1)); label = strings.format(NLS_MATCHES_LOCATION, matchesPosition, matchesCount); } else { label = NLS_NO_RESULTS; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts index 9e0de3979bccd..2b0cb206c90bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts @@ -67,14 +67,14 @@ export class FoldingController extends Disposable implements INotebookEditorCont setFoldingStateDown(index: number, state: CellFoldingState, levels: number) { const doCollapse = state === CellFoldingState.Collapsed; - let region = this._foldingModel!.getRegionAtLine(index + 1); - let regions: FoldingRegion[] = []; + const region = this._foldingModel!.getRegionAtLine(index + 1); + const regions: FoldingRegion[] = []; if (region) { if (region.isCollapsed !== doCollapse) { regions.push(region); } if (levels > 1) { - let regionsInside = this._foldingModel!.getRegionsInside(region, (r, level: number) => r.isCollapsed !== doCollapse && level < levels); + const regionsInside = this._foldingModel!.getRegionsInside(region, (r, level: number) => r.isCollapsed !== doCollapse && level < levels); regions.push(...regionsInside); } } @@ -88,7 +88,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont return; } - let regions = this._foldingModel.getAllRegionsAtLine(index + 1, (region, level) => region.isCollapsed !== (state === CellFoldingState.Collapsed) && level <= levels); + const regions = this._foldingModel.getAllRegionsAtLine(index + 1, (region, level) => region.isCollapsed !== (state === CellFoldingState.Collapsed) && level <= levels); regions.forEach(r => this._foldingModel!.setCollapsed(r.regionIndex, state === CellFoldingState.Collapsed)); this._updateEditorFoldingRanges(); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts index e4a9a41726588..3c2a0f212435d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts @@ -82,7 +82,7 @@ export class FoldingModel implements IDisposable { getRegionAtLine(lineNumber: number): FoldingRegion | null { if (this._regions) { - let index = this._regions.findRange(lineNumber); + const index = this._regions.findRange(lineNumber); if (index >= 0) { return this._regions.toRegion(index); } @@ -91,14 +91,14 @@ export class FoldingModel implements IDisposable { } getRegionsInside(region: FoldingRegion | null, filter?: RegionFilter | RegionFilterWithLevel): FoldingRegion[] { - let result: FoldingRegion[] = []; - let index = region ? region.regionIndex + 1 : 0; - let endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE; + const result: FoldingRegion[] = []; + const index = region ? region.regionIndex + 1 : 0; + const endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE; if (filter && filter.length === 2) { const levelStack: FoldingRegion[] = []; for (let i = index, len = this._regions.length; i < len; i++) { - let current = this._regions.toRegion(i); + const current = this._regions.toRegion(i); if (this._regions.getStartLineNumber(i) < endLineNumber) { while (levelStack.length > 0 && !current.containedBy(levelStack[levelStack.length - 1])) { levelStack.pop(); @@ -113,7 +113,7 @@ export class FoldingModel implements IDisposable { } } else { for (let i = index, len = this._regions.length; i < len; i++) { - let current = this._regions.toRegion(i); + const current = this._regions.toRegion(i); if (this._regions.getStartLineNumber(i) < endLineNumber) { if (!filter || (filter as RegionFilter)(current)) { result.push(current); @@ -127,12 +127,12 @@ export class FoldingModel implements IDisposable { } getAllRegionsAtLine(lineNumber: number, filter?: (r: FoldingRegion, level: number) => boolean): FoldingRegion[] { - let result: FoldingRegion[] = []; + const result: FoldingRegion[] = []; if (this._regions) { let index = this._regions.findRange(lineNumber); let level = 1; while (index >= 0) { - let current = this._regions.toRegion(index); + const current = this._regions.toRegion(index); if (!filter || filter(current, level)) { result.push(current); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts index fe1c2706c22fd..a476da25bbaf2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts @@ -45,14 +45,14 @@ export class ToggleCellToolbarPositionAction extends Action2 { if (['left', 'right', 'hidden'].indexOf(toolbarPosition) >= 0) { // valid position const newViewValue = toolbarPosition === 'right' ? 'left' : 'right'; - let config: { [key: string]: string } = { + const config: { [key: string]: string } = { default: toolbarPosition }; config[viewType] = newViewValue; return config; } else { // invalid position - let config: { [key: string]: string } = { + const config: { [key: string]: string } = { default: 'right', }; config[viewType] = 'left'; @@ -61,7 +61,7 @@ export class ToggleCellToolbarPositionAction extends Action2 { } else { const oldValue = toolbarPosition[viewType] ?? toolbarPosition['default'] ?? 'right'; const newViewValue = oldValue === 'right' ? 'left' : 'right'; - let newConfig = { + const newConfig = { ...toolbarPosition }; newConfig[viewType] = newViewValue; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index a2df48086d026..e0f9401ab0b01 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -92,7 +92,7 @@ export class OutlineEntry { } else { // a markdown cell can inherit markers from its children let topChild: MarkerSeverity | undefined; - for (let child of this.children) { + for (const child of this.children) { child.updateMarkers(markerService); if (child.markerInfo) { topChild = !topChild ? child.markerInfo.topSev : Math.max(child.markerInfo.topSev, topChild); @@ -104,7 +104,7 @@ export class OutlineEntry { clearMarkers(): void { this._markerInfo = undefined; - for (let child of this.children) { + for (const child of this.children) { child.clearMarkers(); } } @@ -114,7 +114,7 @@ export class OutlineEntry { return this; } parents.push(this); - for (let child of this.children) { + for (const child of this.children) { const result = child.find(cell, parents); if (result) { return result; @@ -126,7 +126,7 @@ export class OutlineEntry { asFlatList(bucket: OutlineEntry[]): void { bucket.push(this); - for (let child of this.children) { + for (const child of this.children) { child.asFlatList(bucket); } } @@ -247,12 +247,12 @@ class NotebookQuickPickProvider implements IQuickPickDataSource { getQuickPickElements(): IQuickPickOutlineElement[] { const bucket: OutlineEntry[] = []; - for (let entry of this._getEntries()) { + for (const entry of this._getEntries()) { entry.asFlatList(bucket); } const result: IQuickPickOutlineElement[] = []; const { hasFileIcons } = this._themeService.getFileIconTheme(); - for (let element of bucket) { + for (const element of bucket) { // todo@jrieken it is fishy that codicons cannot be used with iconClasses // but file icons can... result.push({ @@ -359,7 +359,7 @@ export class NotebookCellOutline extends Disposable implements IOutline { - let result: OutlineEntry[] = []; + const result: OutlineEntry[] = []; let candidate = this._activeEntry; while (candidate) { result.unshift(candidate); @@ -458,11 +458,11 @@ export class NotebookCellOutline extends Disposable implements IOutline 0) { - let result: OutlineEntry[] = [entries[0]]; - let parentStack: OutlineEntry[] = [entries[0]]; + const result: OutlineEntry[] = [entries[0]]; + const parentStack: OutlineEntry[] = [entries[0]]; for (let i = 1; i < entries.length; i++) { - let entry = entries[i]; + const entry = entries[i]; while (true) { const len = parentStack.length; @@ -473,7 +473,7 @@ export class NotebookCellOutline extends Disposable implements IOutline { const doUpdateMarker = (clear: boolean) => { - for (let entry of this._entries) { + for (const entry of this._entries) { if (clear) { entry.clearMarkers(); } else { @@ -532,7 +532,7 @@ export class NotebookCellOutline extends Disposable implements IOutline 0) { const cell = notebookEditorWidget.cellAt(notebookEditorWidget.getFocus().start); if (cell) { - for (let entry of this._entries) { + for (const entry of this._entries) { newActive = entry.find(cell, []); if (newActive) { break; @@ -574,7 +574,7 @@ export class NotebookCellOutline extends Disposable implements IOutline { if (viewState) { widget?.restoreListViewState(viewState); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts index 350e3c7e7d13b..42089a40651c9 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts @@ -55,7 +55,7 @@ const profiles = { async function applyProfile(configService: IConfigurationService, profile: Record): Promise { const promises = []; - for (let settingKey in profile) { + for (const settingKey in profile) { promises.push(configService.updateValue(settingKey, profile[settingKey])); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts index 4c646643856f9..94fcf4bcf358f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts @@ -89,7 +89,7 @@ class NotebookViewportContribution extends Disposable implements INotebookEditor } const outputs = viewCell.outputsViewModels; - for (let output of outputs) { + for (const output of outputs) { const [mimeTypes, pick] = output.resolveMimeTypes(this._notebookEditor.textModel!, undefined); if (!mimeTypes.find(mimeType => mimeType.isTrusted) || mimeTypes.length === 0) { continue; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts index 69d1e596fb4cb..657cfe5421304 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts @@ -150,7 +150,7 @@ export function runDeleteAction(editor: IActiveNotebookEditor, cell: ICellViewMo editType: CellEditType.Replace, index: targetCellIndex, count: 1, cells: [] }]; - let finalSelections: ICellRange[] = []; + const finalSelections: ICellRange[] = []; for (let i = 0; i < selections.length; i++) { const selection = selections[i]; @@ -273,7 +273,7 @@ export async function copyCellRange(context: INotebookCellActionContext, directi let range: ICellRange | undefined = undefined; if (context.ui) { - let targetCell = context.cell; + const targetCell = context.cell; const targetCellIndex = editor.getCellIndex(targetCell); range = { start: targetCellIndex, end: targetCellIndex + 1 }; } else { @@ -460,9 +460,9 @@ export async function joinCellsWithSurrounds(bulkEditService: IBulkEditService, const focus = editor.getFocus(); const focusMode = editor.cellAt(focus.start)?.focusMode; - let edits: ResourceEdit[] = []; + const edits: ResourceEdit[] = []; let cell: ICellViewModel | null = null; - let cells: ICellViewModel[] = []; + const cells: ICellViewModel[] = []; for (let i = selections.length - 1; i >= 0; i--) { const selection = selections[i]; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index cd57cb188a04f..ed2a9ee28ba7e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -430,7 +430,7 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction { const viewCell = editor.cellAt(index); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 68383e092960c..88108b67fefc1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1550,7 +1550,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const scrollBottom = scrollTop + Math.max(this._dimension?.height ?? 0, 1080); let offset = 0; - let requests: [ICellViewModel, number][] = []; + const requests: [ICellViewModel, number][] = []; for (let i = 0; i < viewModel.length; i++) { const cell = viewModel.cellAt(i)!; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 735e9201e8ef2..167fc7fb683d0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -253,7 +253,7 @@ export class NotebookProviderInfoStore extends Disposable { getContributedNotebook(resource: URI): readonly NotebookProviderInfo[] { const result: NotebookProviderInfo[] = []; - for (let info of this._contributedEditors.values()) { + for (const info of this._contributedEditors.values()) { if (info.matches(resource)) { result.push(info); } @@ -485,7 +485,7 @@ export class NotebookService extends Disposable implements INotebookService { })); let decorationTriggeredAdjustment = false; - let decorationCheckSet = new Set(); + const decorationCheckSet = new Set(); this._register(this._codeEditorService.onDecorationTypeRegistered(e => { if (decorationTriggeredAdjustment) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts index 3c162b4484e88..d6b9b778514f8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts @@ -116,8 +116,8 @@ export class CellEditorOptions extends CellPart { const editorOptions = deepClone(this.configurationService.getValue('editor', { overrideIdentifier: this.language })); const layoutConfig = this.notebookOptions.getLayoutConfiguration(); const editorOptionsOverrideRaw = layoutConfig.editorOptionsCustomizations ?? {}; - let editorOptionsOverride: { [key: string]: any; } = {}; - for (let key in editorOptionsOverrideRaw) { + const editorOptionsOverride: { [key: string]: any; } = {}; + for (const key in editorOptionsOverrideRaw) { if (key.indexOf('editor.') === 0) { editorOptionsOverride[key.substr(7)] = editorOptionsOverrideRaw[key]; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts index 6583374b3adab..341995eff79f9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts @@ -167,7 +167,7 @@ export class CellEditorStatusBar extends CellPart { const updateItems = (renderedItems: CellStatusBarItem[], newItems: INotebookCellStatusBarItem[], container: HTMLElement) => { if (renderedItems.length > newItems.length) { const deleted = renderedItems.splice(newItems.length, renderedItems.length - newItems.length); - for (let deletedItem of deleted) { + for (const deletedItem of deleted) { container.removeChild(deletedItem.container); deletedItem.dispose(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 736d7b1adc8b3..c80dd927d38c1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -518,7 +518,7 @@ export class NotebookCellList extends WorkbenchList implements ID } private _getVisibleRangesFromIndex(topViewIndex: number, topModelIndex: number, bottomViewIndex: number, bottomModelIndex: number) { - let stack: number[] = []; + const stack: number[] = []; const ranges: ICellRange[] = []; // there are hidden ranges let index = topViewIndex; 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..fc3a5c26bfdec 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1048,7 +1048,7 @@ async function webviewPreloads(ctx: PreloadContext) { public ensureOutputCell(cellId: string, cellTop: number, skipCellTopUpdateIfExist: boolean): OutputCell { let cell = this._outputCells.get(cellId); - let existed = !!cell; + const existed = !!cell; if (!cell) { cell = new OutputCell(cellId); this._outputCells.set(cellId, cell); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index e6a2f0b240318..e43a4d874920c 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -163,7 +163,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod const notebookLayoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); const bottomToolbarDimensions = this.viewContext.notebookOptions.computeBottomToolbarDimensions(); const outputShowMoreContainerHeight = state.outputShowMoreContainerHeight ? state.outputShowMoreContainerHeight : this._layoutInfo.outputShowMoreContainerHeight; - let outputTotalHeight = Math.max(this._outputMinHeight, this.isOutputCollapsed ? notebookLayoutConfiguration.collapsedIndicatorHeight : this._outputsTop!.getTotalSum()); + const outputTotalHeight = Math.max(this._outputMinHeight, this.isOutputCollapsed ? notebookLayoutConfiguration.collapsedIndicatorHeight : this._outputsTop!.getTotalSum()); const originalLayout = this.layoutInfo; if (!this.isInputCollapsed) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 5bc842c742002..b307ff2d557e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -752,7 +752,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD result.push(...ret); }); - for (let _handle in deletesByHandle) { + for (const _handle in deletesByHandle) { const handle = parseInt(_handle); const ids = deletesByHandle[handle]; const cell = this.getCellByHandle(handle); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorDecorations.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorDecorations.ts index 59f9db304a83d..eb50c355dbcb7 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorDecorations.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorDecorations.ts @@ -167,7 +167,7 @@ export class NotebookDecorationCSSRules { private _collectCSSText(opts: any, properties: string[], cssTextArr: string[]): boolean { const lenBefore = cssTextArr.length; - for (let property of properties) { + for (const property of properties) { const value = this._resolveValue(opts[property]); if (typeof value === 'string') { cssTextArr.push(strings.format(_CSS_MAP[property], value)); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index cd45a8dc4feb8..5a2c0e06b1270 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -81,7 +81,7 @@ class FixedLabelStrategy implements IActionLayoutStrategy { const hasToggleMoreAction = lastItemInLeft.action.id === ToggleMenuAction.ID; let size = 0; - let actions: IActionModel[] = []; + const actions: IActionModel[] = []; for (let i = 0; i < primaryActions.length - (hasToggleMoreAction ? 1 : 0); i++) { const actionModel = primaryActions[i]; @@ -165,7 +165,7 @@ class DynamicLabelStrategy implements IActionLayoutStrategy { }; } - let totalWidthWithLabels = actions.map(action => action.size).reduce((a, b) => a + b, 0) + (actions.length - 1) * ACTION_PADDING; + const totalWidthWithLabels = actions.map(action => action.size).reduce((a, b) => a + b, 0) + (actions.length - 1) * ACTION_PADDING; if (totalWidthWithLabels <= leftToolbarContainerMaxWidth) { primaryActions.forEach(action => { action.visible = true; @@ -222,7 +222,7 @@ class DynamicLabelStrategy implements IActionLayoutStrategy { // all actions hidden labels primaryActions.forEach(action => { action.renderLabel = false; }); let size = 0; - let renderActions: IActionModel[] = []; + const renderActions: IActionModel[] = []; for (let i = 0; i < actions.length; i++) { const actionModel = actions[i]; @@ -501,7 +501,7 @@ export class NotebookEditorToolbar extends Disposable { const groups = this._notebookGlobalActionsMenu.getActions({ shouldForwardArgs: true, renderShortTitle: true }); this.domNode.style.display = 'flex'; const primaryLeftGroups = groups.filter(group => /^navigation/.test(group[0])); - let primaryActions: IAction[] = []; + const primaryActions: IAction[] = []; primaryLeftGroups.sort((a, b) => { if (a[0] === 'navigation') { return 1; @@ -539,7 +539,7 @@ export class NotebookEditorToolbar extends Disposable { } private _cacheItemSizes(toolbar: ToolBar) { - let actions: IActionModel[] = []; + const actions: IActionModel[] = []; for (let i = 0; i < toolbar.getItemsLength(); i++) { const action = toolbar.getItemAction(i); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index ba95d6cc1d40d..bc1715e388c42 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -240,11 +240,11 @@ export class NotebookCellTextModel extends Disposable implements ICell { } private _getPersisentMetadata() { - let filteredMetadata: { [key: string]: any; } = {}; + const filteredMetadata: { [key: string]: any; } = {}; const transientCellMetadata = this.transientOptions.transientCellMetadata; const keys = new Set([...Object.keys(this.metadata)]); - for (let key of keys) { + for (const key of keys) { if (!(transientCellMetadata[key as keyof NotebookCellMetadata]) ) { filteredMetadata[key] = this.metadata[key as keyof NotebookCellMetadata]; diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 0f401057ebe47..10283785950a6 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -145,7 +145,7 @@ type TransformedEdit = { export class NotebookEventEmitter extends PauseableEmitter { isDirtyEvent() { - for (let e of this._eventQueue) { + for (const e of this._eventQueue) { for (let i = 0; i < e.rawEvents.length; i++) { if (!e.rawEvents[i].transient) { return true; @@ -236,9 +236,9 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._pauseableEmitter = new NotebookEventEmitter({ merge: (events: NotebookTextModelChangedEvent[]) => { - let first = events[0]; + const first = events[0]; - let rawEvents = first.rawEvents; + const rawEvents = first.rawEvents; let versionId = first.versionId; let endSelectionState = first.endSelectionState; let synchronous = first.synchronous; @@ -560,7 +560,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } private _mergeCellEdits(rawEdits: TransformedEdit[]): TransformedEdit[] { - let mergedEdits: TransformedEdit[] = []; + const mergedEdits: TransformedEdit[] = []; rawEdits.forEach(edit => { if (mergedEdits.length) { @@ -789,7 +789,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel private _isDocumentMetadataChanged(a: NotebookDocumentMetadata, b: NotebookDocumentMetadata) { const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]); - for (let key of keys) { + for (const key of keys) { if (key === 'custom') { if (!this._customMetadataEqual(a[key], b[key]) && @@ -811,7 +811,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel private _isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata) { const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]); - for (let key of keys) { + for (const key of keys) { if ( (a[key as keyof NotebookCellMetadata] !== b[key as keyof NotebookCellMetadata]) && diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d8742a83f788f..0b046a181268a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -860,8 +860,8 @@ export function notebookDocumentFilterMatch(filter: INotebookDocumentFilter, vie } if (filter.filenamePattern) { - let filenamePattern = isDocumentExcludePattern(filter.filenamePattern) ? filter.filenamePattern.include : (filter.filenamePattern as string | glob.IRelativePattern); - let excludeFilenamePattern = isDocumentExcludePattern(filter.filenamePattern) ? filter.filenamePattern.exclude : undefined; + const filenamePattern = isDocumentExcludePattern(filter.filenamePattern) ? filter.filenamePattern.include : (filter.filenamePattern as string | glob.IRelativePattern); + const excludeFilenamePattern = isDocumentExcludePattern(filter.filenamePattern) ? filter.filenamePattern.exclude : undefined; if (glob.match(filenamePattern, basename(resource.fsPath).toLowerCase())) { if (excludeFilenamePattern) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 7833bccc672f2..5d2c015c4625e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -237,7 +237,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook private async getUntitledDocumentData(resource: URI): Promise { // If it's an untitled file we must populate the untitledDocumentData const untitledString = this.untitledTextEditorService.getValue(resource); - let untitledDocumentData = untitledString ? VSBuffer.fromString(untitledString) : undefined; + const untitledDocumentData = untitledString ? VSBuffer.fromString(untitledString) : undefined; return untitledDocumentData; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts index d6df1d0e94cb4..f5d98feee2659 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts @@ -171,7 +171,7 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes const suffix = NotebookProviderInfo.possibleFileEnding(info.selectors) ?? ''; for (let counter = 1; ; counter++) { - let candidate = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}${suffix}`, query: viewType }); + const candidate = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}${suffix}`, query: viewType }); if (!this._notebookService.getNotebookTextModel(candidate)) { resource = candidate; break; diff --git a/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts b/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts index 4d1ca9e902d07..bd59c7bfbf1c6 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts @@ -14,7 +14,7 @@ const perfMarks = new Map(); export function mark(resource: URI, name: PerfName): void { const key = resource.toString(); if (!perfMarks.has(key)) { - let perfMark: PerformanceMark = {}; + const perfMark: PerformanceMark = {}; perfMark[name] = Date.now(); perfMarks.set(key, perfMark); } else { diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts index 4a1c386ca3184..f3ef53e63da0c 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -90,8 +90,8 @@ export class NotebookProviderInfo { return false; } - let filenamePattern = selector.include; - let excludeFilenamePattern = selector.exclude; + const filenamePattern = selector.include; + const excludeFilenamePattern = selector.exclude; if (glob.match(filenamePattern, basename(resource.fsPath).toLowerCase())) { if (excludeFilenamePattern) { @@ -106,7 +106,7 @@ export class NotebookProviderInfo { } static possibleFileEnding(selectors: NotebookSelector[]): string | undefined { - for (let selector of selectors) { + for (const selector of selectors) { const ending = NotebookProviderInfo._possibleFileEnding(selector); if (ending) { return ending; diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts index b37f37caf164b..81e363fdba12b 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts @@ -75,7 +75,7 @@ export class NotebookEditorModelManager extends Disposable { public ensureSyncedResources(resources: URI[]): void { for (const resource of resources) { - let resourceStr = resource.toString(); + const resourceStr = resource.toString(); if (!this._syncedModels[resourceStr]) { this._beginModelSync(resource); @@ -87,12 +87,12 @@ export class NotebookEditorModelManager extends Disposable { } private _beginModelSync(resource: URI): void { - let model = this._notebookService.listNotebookDocuments().find(document => document.uri.toString() === resource.toString()); + const model = this._notebookService.listNotebookDocuments().find(document => document.uri.toString() === resource.toString()); if (!model) { return; } - let modelUrl = resource.toString(); + const modelUrl = resource.toString(); this._proxy.acceptNewModel( model.uri.toString(), @@ -171,7 +171,7 @@ export class NotebookEditorModelManager extends Disposable { } private _stopModelSync(modelUrl: string): void { - let toDispose = this._syncedModels[modelUrl]; + const toDispose = this._syncedModels[modelUrl]; delete this._syncedModels[modelUrl]; delete this._syncedModelsLastUsedTime[modelUrl]; dispose(toDispose); From 16523a3f86c50042e73553db2a95bc37279061fb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Dec 2021 13:18:03 -0800 Subject: [PATCH 14/70] substr -> substring in notebooks --- .../notebook/browser/contrib/outline/notebookOutline.ts | 2 +- .../contrib/notebook/browser/notebook.contribution.ts | 2 +- .../contrib/notebook/browser/notebookKernelServiceImpl.ts | 4 ++-- .../notebook/browser/view/cellParts/cellEditorOptions.ts | 2 +- .../notebook/browser/view/renderers/webviewPreloads.ts | 2 +- .../contrib/notebook/common/model/notebookTextModel.ts | 2 +- src/vs/workbench/contrib/notebook/common/notebookCommon.ts | 6 +++--- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index e0f9401ab0b01..2793a3ce11031 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -419,7 +419,7 @@ export class NotebookCellOutline extends Disposable implements IOutline { - return cell.textBuffer.getLineContent(1).substr(0, limit); + return cell.textBuffer.getLineContent(1).substring(0, limit); } }; const languageId = this._languageService.getLanguageIdByLanguageName(cell.language); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts index 02452f1149b71..a576297af56cb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -37,8 +37,8 @@ class NotebookTextModelLikeId { static obj(s: string): INotebookTextModelLike { const idx = s.indexOf('/'); return { - viewType: s.substr(0, idx), - uri: URI.parse(s.substr(idx + 1)) + viewType: s.substring(0, idx), + uri: URI.parse(s.substring(idx + 1)) }; } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts index d6b9b778514f8..48436cd345f8e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts @@ -119,7 +119,7 @@ export class CellEditorOptions extends CellPart { const editorOptionsOverride: { [key: string]: any; } = {}; for (const key in editorOptionsOverrideRaw) { if (key.indexOf('editor.') === 0) { - editorOptionsOverride[key.substr(7)] = editorOptionsOverrideRaw[key]; + editorOptionsOverride[key.substring(7)] = editorOptionsOverrideRaw[key]; } } const computed = { 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 fc3a5c26bfdec..3f0ef6c528711 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -76,7 +76,7 @@ async function webviewPreloads(ctx: PreloadContext) { handleDataUrl(node.href, node.download); } else if (node.hash && node.getAttribute('href') === node.hash) { // Scrolling to location within current doc - const targetId = node.hash.substr(1, node.hash.length - 1); + const targetId = node.hash.substring(1); // Check outer document first let scrollTarget: Element | null | undefined = event.view.document.getElementById(targetId); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 10283785950a6..427dfc2104544 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -687,7 +687,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel private _overwriteAlternativeVersionId(newAlternativeVersionId: string): void { this._alternativeVersionId = newAlternativeVersionId; - this._notebookSpecificAlternativeId = Number(newAlternativeVersionId.substr(0, newAlternativeVersionId.indexOf('_'))); + this._notebookSpecificAlternativeId = Number(newAlternativeVersionId.substring(0, newAlternativeVersionId.indexOf('_'))); } private _updateNotebookMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 0b046a181268a..50bdbb8ab3f45 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -524,7 +524,7 @@ export namespace CellUri { return { handle, notebook: cell.with({ - scheme: cell.fragment.substr(match[0].length) || Schemas.file, + scheme: cell.fragment.substring(match[0].length) || Schemas.file, fragment: null }) }; @@ -578,7 +578,7 @@ export namespace CellUri { return { handle, notebook: metadata.with({ - scheme: metadata.fragment.substr(match[0].length) || Schemas.file, + scheme: metadata.fragment.substring(match[0].length) || Schemas.file, fragment: null }) }; @@ -967,7 +967,7 @@ export class NotebookWorkingCopyTypeIdentifier { static parse(candidate: string): string | undefined { if (candidate.startsWith(NotebookWorkingCopyTypeIdentifier._prefix)) { - return candidate.substr(NotebookWorkingCopyTypeIdentifier._prefix.length); + return candidate.substring(NotebookWorkingCopyTypeIdentifier._prefix.length); } return undefined; } From 2933672219cdeb4e4fc04134f332fd2903bb2c56 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Dec 2021 15:51:44 -0800 Subject: [PATCH 15/70] 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 16/70] 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 17/70] 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 18/70] 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 19/70] 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 20/70] 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 21/70] 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 faaeb6be4130ccd9d0e4379f6bde4ab919b23990 Mon Sep 17 00:00:00 2001 From: Sam Estep Date: Sun, 2 Jan 2022 23:11:16 -0500 Subject: [PATCH 22/70] 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 23/70] 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 24/70] 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 25/70] 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 26/70] 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 27/70] 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 28/70] :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 29/70] 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 30/70] 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 31/70] 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 32/70] 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 33/70] 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 34/70] 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 35/70] 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 36/70] 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 37/70] 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 38/70] 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 39/70] 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 40/70] 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 41/70] 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 7f906a2efef358014da9e80672bb44bf7c07abfc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 3 Jan 2022 17:46:01 -0800 Subject: [PATCH 42/70] 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 43/70] 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 44/70] 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 45/70] 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 46/70] 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 47/70] 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 48/70] 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 49/70] 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 50/70] 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 51/70] `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 52/70] 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 53/70] 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 54/70] 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 55/70] :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 56/70] 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 57/70] 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 f8d5df4621094b80954057a9ee7f397a4c26bc4e Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Tue, 4 Jan 2022 16:11:51 +0100 Subject: [PATCH 58/70] 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 59/70] 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 60/70] 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 61/70] 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 62/70] 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 63/70] 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 64/70] 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 65/70] 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 66/70] 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 67/70] 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 68/70] 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 69/70] 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 70/70] =?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 {