From 11f523c6d792273b5d00a474a9d3aaed5b18bc6c Mon Sep 17 00:00:00 2001 From: mjcrouch Date: Tue, 18 Feb 2020 23:03:03 +0000 Subject: [PATCH 001/695] Support `scmResourceState` in `when` clauses fixes #86180 * Adds `contextValue?: string` to the SourceControlResourceState * Allows when clause in scm/resourceState/context menus to use scmResourceState --- src/vs/vscode.proposed.d.ts | 26 +++++++++++++ src/vs/workbench/api/browser/mainThreadSCM.ts | 8 ++-- .../workbench/api/common/extHost.protocol.ts | 3 +- src/vs/workbench/api/common/extHostSCM.ts | 3 +- src/vs/workbench/contrib/scm/browser/menus.ts | 39 +++++++++++++------ .../contrib/scm/browser/repositoryPane.ts | 8 +++- src/vs/workbench/contrib/scm/common/scm.ts | 1 + 7 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 637dff30817..426c2f1ed1e 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1734,4 +1734,30 @@ declare module 'vscode' { } //#endregion + + //#region Support `scmResourceState` in `when` clauses #86180 https://github.com/microsoft/vscode/issues/86180 + + export interface SourceControlResourceState { + /** + * Context value of the resource state. This can be used to contribute resource specific actions. + * For example, if a resource is given a context value as `diffable`. When contributing actions to `scm/resourceState/context` + * using `menus` extension point, you can specify context value for key `scmResourceState` in `when` expressions, like `scmResourceState == diffable`. + * ``` + * "contributes": { + * "menus": { + * "scm/resourceState/context": [ + * { + * "command": "extension.diff", + * "when": "scmResourceState == diffable" + * } + * ] + * } + * } + * ``` + * This will show action `extension.diff` only for resources with `contextValue` is `diffable`. + */ + readonly contextValue?: string; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index e75ac0b4da3..68b6e9cbaea 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -68,7 +68,8 @@ class MainThreadSCMResource implements ISCMResource { private readonly handle: number, public sourceUri: URI, public resourceGroup: ISCMResourceGroup, - public decorations: ISCMResourceDecorations + public decorations: ISCMResourceDecorations, + public contextValue: string ) { } open(): Promise { @@ -198,7 +199,7 @@ class MainThreadSCMProvider implements ISCMProvider { for (const [start, deleteCount, rawResources] of groupSlices) { const resources = rawResources.map(rawResource => { - const [handle, sourceUri, icons, tooltip, strikeThrough, faded] = rawResource; + const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] = rawResource; const icon = icons[0]; const iconDark = icons[1] || icon; const decorations = { @@ -216,7 +217,8 @@ class MainThreadSCMProvider implements ISCMProvider { handle, URI.revive(sourceUri), group, - decorations + decorations, + contextValue ); }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ee4368dbe33..1a141cd9bf2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -715,7 +715,8 @@ export type SCMRawResource = [ UriComponents[] /*icons: light, dark*/, string /*tooltip*/, boolean /*strike through*/, - boolean /*faded*/ + boolean /*faded*/, + string /*context value*/ ]; export type SCMRawResourceSplice = [ diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 34a3138ad32..0fdfbd44923 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -306,8 +306,9 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG const tooltip = (r.decorations && r.decorations.tooltip) || ''; const strikeThrough = r.decorations && !!r.decorations.strikeThrough; const faded = r.decorations && !!r.decorations.faded; + const contextValue = r.contextValue || ''; - const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded] as SCMRawResource; + const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] as SCMRawResource; return { rawResource, handle }; }); diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 327801803c2..cd1f114db4f 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -27,10 +27,13 @@ interface ISCMResourceGroupMenuEntry { interface ISCMMenus { readonly resourceGroupMenu: IMenu; - readonly resourceMenu: IMenu; readonly resourceFolderMenu: IMenu; } +interface ISCMResourceMenu extends IDisposable { + readonly menu: IMenu; +} + export function getSCMResourceContextKey(resource: ISCMResourceGroup | ISCMResource): string { return isSCMResource(resource) ? resource.resourceGroup.id : resource.id; } @@ -111,6 +114,10 @@ export class SCMMenus implements IDisposable { return this.getActions(MenuId.SCMResourceContext, resource).secondary; } + getResourceInlineActions(resource: ISCMResource): IAction[] { + return this.getActions(MenuId.SCMResourceContext, resource).primary; + } + getResourceFolderContextActions(group: ISCMResourceGroup): IAction[] { return this.getActions(MenuId.SCMResourceFolderContext, group).secondary; } @@ -118,6 +125,9 @@ export class SCMMenus implements IDisposable { private getActions(menuId: MenuId, resource: ISCMResourceGroup | ISCMResource): { primary: IAction[]; secondary: IAction[]; } { const contextKeyService = this.contextKeyService.createScoped(); contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(resource)); + if (isSCMResource(resource)) { + contextKeyService.createKey('scmResourceState', resource.contextValue); + } const menu = this.menuService.createMenu(menuId, contextKeyService); const primary: IAction[] = []; @@ -131,6 +141,20 @@ export class SCMMenus implements IDisposable { return result; } + createResourceMenu(group: ISCMResourceGroup, resource: ISCMResource): ISCMResourceMenu { + const contextKeyService = this.contextKeyService.createScoped(); + contextKeyService.createKey('scmProvider', group.provider.contextValue); + contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(resource)); + contextKeyService.createKey('scmResourceState', resource.contextValue); + + const menu = this.menuService.createMenu(MenuId.SCMResourceContext, contextKeyService); + + const disposable = combinedDisposable(menu, contextKeyService); + const dispose = () => disposable.dispose(); + + return { menu, dispose }; + } + getResourceGroupMenu(group: ISCMResourceGroup): IMenu { if (!this.resourceGroupMenus.has(group)) { throw new Error('SCM Resource Group menu not found'); @@ -139,14 +163,6 @@ export class SCMMenus implements IDisposable { return this.resourceGroupMenus.get(group)!.resourceGroupMenu; } - getResourceMenu(group: ISCMResourceGroup): IMenu { - if (!this.resourceGroupMenus.has(group)) { - throw new Error('SCM Resource Group menu not found'); - } - - return this.resourceGroupMenus.get(group)!.resourceMenu; - } - getResourceFolderMenu(group: ISCMResourceGroup): IMenu { if (!this.resourceGroupMenus.has(group)) { throw new Error('SCM Resource Group menu not found'); @@ -162,11 +178,10 @@ export class SCMMenus implements IDisposable { contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(group)); const resourceGroupMenu = this.menuService.createMenu(MenuId.SCMResourceGroupContext, contextKeyService); - const resourceMenu = this.menuService.createMenu(MenuId.SCMResourceContext, contextKeyService); const resourceFolderMenu = this.menuService.createMenu(MenuId.SCMResourceFolderContext, contextKeyService); - const disposable = combinedDisposable(contextKeyService, resourceGroupMenu, resourceMenu, resourceFolderMenu); + const disposable = combinedDisposable(contextKeyService, resourceGroupMenu, resourceFolderMenu); - this.resourceGroupMenus.set(group, { resourceGroupMenu, resourceMenu, resourceFolderMenu }); + this.resourceGroupMenus.set(group, { resourceGroupMenu, resourceFolderMenu }); return { group, disposable }; }); diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index ab9c041d125..e015b62a8d9 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -220,7 +220,9 @@ class ResourceRenderer implements ICompressibleTreeRenderer; } From 3e41413b74ccf293b4c4fc05ac7843331e4ebd1a Mon Sep 17 00:00:00 2001 From: mjcrouch Date: Wed, 19 Feb 2020 09:33:37 +0000 Subject: [PATCH 002/695] remove unused function --- src/vs/workbench/contrib/scm/browser/menus.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index cd1f114db4f..6934d7c4de3 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -114,10 +114,6 @@ export class SCMMenus implements IDisposable { return this.getActions(MenuId.SCMResourceContext, resource).secondary; } - getResourceInlineActions(resource: ISCMResource): IAction[] { - return this.getActions(MenuId.SCMResourceContext, resource).primary; - } - getResourceFolderContextActions(group: ISCMResourceGroup): IAction[] { return this.getActions(MenuId.SCMResourceFolderContext, group).secondary; } From 82ddc5acf6bbb3830920a5f4b00f687955c5969c Mon Sep 17 00:00:00 2001 From: mjcrouch Date: Wed, 19 Feb 2020 19:17:18 +0000 Subject: [PATCH 003/695] Only create one menu per context value per group --- src/vs/workbench/contrib/scm/browser/menus.ts | 24 ++++++++++++++++--- .../contrib/scm/browser/repositoryPane.ts | 8 ++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 6934d7c4de3..96b01219235 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -28,6 +28,7 @@ interface ISCMResourceGroupMenuEntry { interface ISCMMenus { readonly resourceGroupMenu: IMenu; readonly resourceFolderMenu: IMenu; + readonly resourceMenusByContext: Map; } interface ISCMResourceMenu extends IDisposable { @@ -137,7 +138,7 @@ export class SCMMenus implements IDisposable { return result; } - createResourceMenu(group: ISCMResourceGroup, resource: ISCMResource): ISCMResourceMenu { + private createResourceMenu(group: ISCMResourceGroup, resource: ISCMResource): ISCMResourceMenu { const contextKeyService = this.contextKeyService.createScoped(); contextKeyService.createKey('scmProvider', group.provider.contextValue); contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(resource)); @@ -159,6 +160,19 @@ export class SCMMenus implements IDisposable { return this.resourceGroupMenus.get(group)!.resourceGroupMenu; } + getResourceMenu(group: ISCMResourceGroup, resource: ISCMResource): IMenu { + if (!this.resourceGroupMenus.has(group)) { + throw new Error('SCM Resource Group menu not found'); + } + + const foundGroup = this.resourceGroupMenus.get(group)!; + if (!foundGroup.resourceMenusByContext.has(resource.contextValue)) { + foundGroup.resourceMenusByContext.set(resource.contextValue, this.createResourceMenu(group, resource)); + } + + return foundGroup.resourceMenusByContext.get(resource.contextValue)!.menu; + } + getResourceFolderMenu(group: ISCMResourceGroup): IMenu { if (!this.resourceGroupMenus.has(group)) { throw new Error('SCM Resource Group menu not found'); @@ -175,9 +189,13 @@ export class SCMMenus implements IDisposable { const resourceGroupMenu = this.menuService.createMenu(MenuId.SCMResourceGroupContext, contextKeyService); const resourceFolderMenu = this.menuService.createMenu(MenuId.SCMResourceFolderContext, contextKeyService); - const disposable = combinedDisposable(contextKeyService, resourceGroupMenu, resourceFolderMenu); - this.resourceGroupMenus.set(group, { resourceGroupMenu, resourceFolderMenu }); + const resourceMenusByContext = new Map(); + const resourceMenusDisposable = { dispose: () => resourceMenusByContext.forEach(menu => menu.dispose()) }; + + const disposable = combinedDisposable(contextKeyService, resourceGroupMenu, resourceFolderMenu, resourceMenusDisposable); + + this.resourceGroupMenus.set(group, { resourceGroupMenu, resourceFolderMenu, resourceMenusByContext }); return { group, disposable }; }); diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index e015b62a8d9..584754f1b69 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -220,9 +220,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer Date: Thu, 9 Apr 2020 22:19:00 -0400 Subject: [PATCH 004/695] format for upstream --- src/vs/editor/common/config/editorOptions.ts | 11 +++++++++++ src/vs/editor/contrib/find/findModel.ts | 2 +- src/vs/monaco.d.ts | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 03354b46bce..fd731e97daf 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1236,6 +1236,10 @@ class EditorEmptySelectionClipboard extends EditorBooleanOption constructor() { const defaults: EditorFindOptions = { + moveOnType: true, seedSearchStringFromSelection: true, autoFindInSelection: 'never', globalFindClipboard: false, @@ -1274,6 +1279,11 @@ class EditorFind extends BaseEditorOption super( EditorOption.find, 'find', defaults, { + 'editor.find.moveOnType': { + type: 'boolean', + default: defaults.moveOnType, + description: nls.localize('find.moveOnType', "Controls whether the cursor should move to find matches while typing.") + }, 'editor.find.seedSearchStringFromSelection': { type: 'boolean', default: defaults.seedSearchStringFromSelection, @@ -1317,6 +1327,7 @@ class EditorFind extends BaseEditorOption } const input = _input as IEditorFindOptions; return { + moveOnType: EditorBooleanOption.boolean(input.moveOnType, this.defaultValue.moveOnType), seedSearchStringFromSelection: EditorBooleanOption.boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection), autoFindInSelection: typeof _input.autoFindInSelection === 'boolean' ? (_input.autoFindInSelection ? 'always' : 'never') diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index c083478dd00..a0816803467 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -195,7 +195,7 @@ export class FindModelBoundToEditorModel { undefined ); - if (moveCursor) { + if (moveCursor && this._editor.getOption(EditorOption.find).moveOnType) { this._moveToNextMatch(this._decorations.getStartPosition()); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 99d3a926bea..693f2b2a3f0 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3266,6 +3266,10 @@ declare namespace monaco.editor { * Configuration options for editor find widget */ export interface IEditorFindOptions { + /** + * Controls whether the cursor should move to find matches while typing. + */ + moveOnType?: boolean; /** * Controls if we seed search string in the Find Widget with editor selection. */ From 91c1a43f07ec4e42bde51545fc0f40c6684aef04 Mon Sep 17 00:00:00 2001 From: Ted Goldman Date: Fri, 10 Apr 2020 20:45:39 -0400 Subject: [PATCH 005/695] debounced Find Widget, preventing cursor movement until typing is finished --- src/vs/editor/common/config/editorOptions.ts | 2 +- src/vs/editor/contrib/find/findModel.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index fd731e97daf..4c4d1401569 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1282,7 +1282,7 @@ class EditorFind extends BaseEditorOption 'editor.find.moveOnType': { type: 'boolean', default: defaults.moveOnType, - description: nls.localize('find.moveOnType', "Controls whether the cursor should move to find matches while typing.") + description: nls.localize('find.moveOnType', "Controls whether the cursor should jump to find matches while typing.") }, 'editor.find.seedSearchStringFromSelection': { type: 'boolean', diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index a0816803467..bc80cedf4f2 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -69,6 +69,9 @@ export const FIND_IDS = { export const MATCHES_LIMIT = 19999; const RESEARCH_DELAY = 240; +let debounceTimer: ReturnType; +let DEBOUNCE_DURATION = 240; + export class FindModelBoundToEditorModel { private readonly _editor: IActiveCodeEditor; @@ -168,7 +171,7 @@ export class FindModelBoundToEditorModel { return model.getFullModelRange(); } - private research(moveCursor: boolean, newFindScope?: Range | null): void { + private research (moveCursor: boolean, newFindScope?: Range | null): void { let findScope: Range | null = null; if (typeof newFindScope !== 'undefined') { findScope = newFindScope; @@ -196,7 +199,10 @@ export class FindModelBoundToEditorModel { ); if (moveCursor && this._editor.getOption(EditorOption.find).moveOnType) { - this._moveToNextMatch(this._decorations.getStartPosition()); + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + this._moveToNextMatch(this._decorations.getStartPosition()); + }, DEBOUNCE_DURATION); } } From b9efdab4831b6b69c13e4b15b55d519c65d0e2d4 Mon Sep 17 00:00:00 2001 From: Jody Heavener Date: Sat, 30 May 2020 00:57:56 -0400 Subject: [PATCH 006/695] refactor find widget to support multiple selection find/replace --- src/vs/editor/common/model.ts | 4 +- src/vs/editor/common/model/textModel.ts | 26 ++++-- src/vs/editor/contrib/find/findController.ts | 28 ++++--- src/vs/editor/contrib/find/findDecorations.ts | 41 ++++++---- src/vs/editor/contrib/find/findModel.ts | 61 ++++++++------ src/vs/editor/contrib/find/findState.ts | 12 ++- src/vs/editor/contrib/find/findWidget.ts | 47 +++++++---- .../contrib/find/test/findController.test.ts | 24 +++--- .../contrib/find/test/findModel.test.ts | 81 ++++++++++++++++++- .../editor/contrib/multicursor/multicursor.ts | 14 ++-- src/vs/monaco.d.ts | 4 +- 11 files changed, 246 insertions(+), 96 deletions(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 122ef954003..5e4661bc8f4 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -800,7 +800,7 @@ export interface ITextModel { /** * Search the model. * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchScope Limit the searching to only search inside this range. + * @param searchScope Limit the searching to only search inside these ranges. * @param isRegex Used to indicate that `searchString` is a regular expression. * @param matchCase Force the matching to match lower/upper case exactly. * @param wordSeparators Force the matching to match entire words only. Pass null otherwise. @@ -808,7 +808,7 @@ export interface ITextModel { * @param limitResultCount Limit the number of results * @return The ranges where the matches are. It is empty if no matches have been found. */ - findMatches(searchString: string, searchScope: IRange, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount?: number): FindMatch[]; + findMatches(searchString: string, searchScope: IRange | IRange[], isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount?: number): FindMatch[]; /** * Search the model for the next match. Loops to the beginning of the model if needed. * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 7e48e64a271..b9fb2d21f28 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1112,13 +1112,23 @@ export class TextModel extends Disposable implements model.ITextModel { public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] { this._assertNotDisposed(); - let searchRange: Range; - if (Range.isIRange(rawSearchScope)) { - searchRange = this.validateRange(rawSearchScope); - } else { - searchRange = this.getFullModelRange(); + let searchRanges: Range[] | null = null; + + if (rawSearchScope !== null) { + if (!Array.isArray(rawSearchScope)) { + rawSearchScope = [rawSearchScope]; + } + + if (rawSearchScope.every((searchScope: Range) => Range.isIRange(searchScope))) { + searchRanges = rawSearchScope.map((searchScope: Range) => this.validateRange(searchScope)); + } } + if (searchRanges === null) { + searchRanges = [this.getFullModelRange()]; + } + + let matchMapper: (value: Range, index: number, array: Range[]) => model.FindMatch[]; if (!isRegex && searchString.indexOf('\n') < 0) { // not regex, not multi line const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators); @@ -1128,10 +1138,12 @@ export class TextModel extends Disposable implements model.ITextModel { return []; } - return this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount); + matchMapper = (searchRange: Range) => this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount); + } else { + matchMapper = (searchRange: Range) => TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount); } - return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount); + return searchRanges.map(matchMapper).reduce((arr, matches: model.FindMatch[]) => arr.concat(matches), []); } public findNextMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): model.FindMatch | null { diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 8d2f6668924..62a132c823c 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -233,12 +233,22 @@ export class CommonFindController extends Disposable implements IEditorContribut this._state.change({ searchScope: null }, true); } else { if (this._editor.hasModel()) { - let selection = this._editor.getSelection(); - if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { - selection = selection.setEndPosition(selection.endLineNumber - 1, this._editor.getModel().getLineMaxColumn(selection.endLineNumber - 1)); - } - if (!selection.isEmpty()) { - this._state.change({ searchScope: selection }, true); + let selections = this._editor.getSelections(); + selections.map(selection => { + if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { + selection = selection.setEndPosition( + selection.endLineNumber - 1, + this._editor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1) + ); + } + if (!selection.isEmpty()) { + return selection; + } + return null; + }).filter(element => !!element); + + if (selections.length) { + this._state.change({ searchScope: selections }, true); } } } @@ -293,9 +303,9 @@ export class CommonFindController extends Disposable implements IEditorContribut } if (opts.updateSearchScope) { - let currentSelection = this._editor.getSelection(); - if (!currentSelection.isEmpty()) { - stateChanges.searchScope = currentSelection; + let currentSelections = this._editor.getSelections(); + if (currentSelections.some(selection => !selection.isEmpty())) { + stateChanges.searchScope = currentSelections; } } diff --git a/src/vs/editor/contrib/find/findDecorations.ts b/src/vs/editor/contrib/find/findDecorations.ts index a3f137d1575..c6b966e77c1 100644 --- a/src/vs/editor/contrib/find/findDecorations.ts +++ b/src/vs/editor/contrib/find/findDecorations.ts @@ -17,7 +17,7 @@ export class FindDecorations implements IDisposable { private readonly _editor: IActiveCodeEditor; private _decorations: string[]; private _overviewRulerApproximateDecorations: string[]; - private _findScopeDecorationId: string | null; + private _findScopeDecorationIds: string[]; private _rangeHighlightDecorationId: string | null; private _highlightedDecorationId: string | null; private _startPosition: Position; @@ -26,7 +26,7 @@ export class FindDecorations implements IDisposable { this._editor = editor; this._decorations = []; this._overviewRulerApproximateDecorations = []; - this._findScopeDecorationId = null; + this._findScopeDecorationIds = []; this._rangeHighlightDecorationId = null; this._highlightedDecorationId = null; this._startPosition = this._editor.getPosition(); @@ -37,7 +37,7 @@ export class FindDecorations implements IDisposable { this._decorations = []; this._overviewRulerApproximateDecorations = []; - this._findScopeDecorationId = null; + this._findScopeDecorationIds = []; this._rangeHighlightDecorationId = null; this._highlightedDecorationId = null; } @@ -45,7 +45,7 @@ export class FindDecorations implements IDisposable { public reset(): void { this._decorations = []; this._overviewRulerApproximateDecorations = []; - this._findScopeDecorationId = null; + this._findScopeDecorationIds = []; this._rangeHighlightDecorationId = null; this._highlightedDecorationId = null; } @@ -54,9 +54,22 @@ export class FindDecorations implements IDisposable { return this._decorations.length; } + /** @deprecated use getFindScopes to support multiple selections */ public getFindScope(): Range | null { - if (this._findScopeDecorationId) { - return this._editor.getModel().getDecorationRange(this._findScopeDecorationId); + if (this._findScopeDecorationIds[0]) { + return this._editor.getModel().getDecorationRange(this._findScopeDecorationIds[0]); + } + return null; + } + + public getFindScopes(): Range[] | null { + if (this._findScopeDecorationIds.length) { + const scopes = this._findScopeDecorationIds.map(findScopeDecorationId => + this._editor.getModel().getDecorationRange(findScopeDecorationId) + ).filter(element => !!element); + if (scopes.length) { + return scopes as Range[]; + } } return null; } @@ -133,7 +146,7 @@ export class FindDecorations implements IDisposable { return matchPosition; } - public set(findMatches: FindMatch[], findScope: Range | null): void { + public set(findMatches: FindMatch[], findScopes: Range[] | null): void { this._editor.changeDecorations((accessor) => { let findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION; @@ -195,12 +208,12 @@ export class FindDecorations implements IDisposable { } // Find scope - if (this._findScopeDecorationId) { - accessor.removeDecoration(this._findScopeDecorationId); - this._findScopeDecorationId = null; + if (this._findScopeDecorationIds.length) { + this._findScopeDecorationIds.forEach(findScopeDecorationId => accessor.removeDecoration(findScopeDecorationId)); + this._findScopeDecorationIds = []; } - if (findScope) { - this._findScopeDecorationId = accessor.addDecoration(findScope, FindDecorations._FIND_SCOPE_DECORATION); + if (findScopes?.length) { + this._findScopeDecorationIds = findScopes.map(findScope => accessor.addDecoration(findScope, FindDecorations._FIND_SCOPE_DECORATION)); } }); } @@ -253,8 +266,8 @@ export class FindDecorations implements IDisposable { let result: string[] = []; result = result.concat(this._decorations); result = result.concat(this._overviewRulerApproximateDecorations); - if (this._findScopeDecorationId) { - result.push(this._findScopeDecorationId); + if (this._findScopeDecorationIds.length) { + result.push(...this._findScopeDecorationIds); } if (this._rangeHighlightDecorationId) { result.push(this._rangeHighlightDecorationId); diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index c083478dd00..38efb9dc70b 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -168,26 +168,36 @@ export class FindModelBoundToEditorModel { return model.getFullModelRange(); } - private research(moveCursor: boolean, newFindScope?: Range | null): void { - let findScope: Range | null = null; + private research(moveCursor: boolean, newFindScope?: Range | Range[] | null): void { + let findScopes: Range[] | null = null; if (typeof newFindScope !== 'undefined') { - findScope = newFindScope; - } else { - findScope = this._decorations.getFindScope(); - } - if (findScope !== null) { - if (findScope.startLineNumber !== findScope.endLineNumber) { - if (findScope.endColumn === 1) { - findScope = new Range(findScope.startLineNumber, 1, findScope.endLineNumber - 1, this._editor.getModel().getLineMaxColumn(findScope.endLineNumber - 1)); + if (newFindScope !== null) { + if (!Array.isArray(newFindScope)) { + findScopes = [newFindScope as Range]; } else { - // multiline find scope => expand to line starts / ends - findScope = new Range(findScope.startLineNumber, 1, findScope.endLineNumber, this._editor.getModel().getLineMaxColumn(findScope.endLineNumber)); + findScopes = newFindScope; } } + } else { + findScopes = this._decorations.getFindScopes(); + } + if (findScopes !== null) { + findScopes = findScopes.map(findScope => { + if (findScope.startLineNumber !== findScope.endLineNumber) { + let endLineNumber = findScope.endLineNumber; + + if (findScope.endColumn === 1) { + endLineNumber = endLineNumber - 1; + } + + return new Range(findScope.startLineNumber, 1, endLineNumber, this._editor.getModel().getLineMaxColumn(endLineNumber)); + } + return findScope; + }); } - let findMatches = this._findMatches(findScope, false, MATCHES_LIMIT); - this._decorations.set(findMatches, findScope); + let findMatches = this._findMatches(findScopes, false, MATCHES_LIMIT); + this._decorations.set(findMatches, findScopes); this._state.changeMatchInfo( this._decorations.getCurrentMatchesPosition(this._editor.getSelection()), @@ -443,9 +453,12 @@ export class FindModelBoundToEditorModel { } } - private _findMatches(findScope: Range | null, captureMatches: boolean, limitResultCount: number): FindMatch[] { - let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), findScope); - return this._editor.getModel().findMatches(this._state.searchString, searchRange, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(EditorOption.wordSeparators) : null, captureMatches, limitResultCount); + private _findMatches(findScopes: Range[] | null, captureMatches: boolean, limitResultCount: number): FindMatch[] { + const searchRanges = (findScopes as [] || [null]).map((scope: Range | null) => + FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), scope) + ); + + return this._editor.getModel().findMatches(this._state.searchString, searchRanges, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getOption(EditorOption.wordSeparators) : null, captureMatches, limitResultCount); } public replaceAll(): void { @@ -453,13 +466,13 @@ export class FindModelBoundToEditorModel { return; } - const findScope = this._decorations.getFindScope(); + const findScopes = this._decorations.getFindScopes(); - if (findScope === null && this._state.matchesCount >= MATCHES_LIMIT) { + if (findScopes === null && this._state.matchesCount >= MATCHES_LIMIT) { // Doing a replace on the entire file that is over ${MATCHES_LIMIT} matches this._largeReplaceAll(); } else { - this._regularReplaceAll(findScope); + this._regularReplaceAll(findScopes); } this.research(false); @@ -504,10 +517,10 @@ export class FindModelBoundToEditorModel { this._executeEditorCommand('replaceAll', command); } - private _regularReplaceAll(findScope: Range | null): void { + private _regularReplaceAll(findScopes: Range[] | null): void { const replacePattern = this._getReplacePattern(); // Get all the ranges (even more than the highlighted ones) - let matches = this._findMatches(findScope, replacePattern.hasReplacementPatterns || this._state.preserveCase, Constants.MAX_SAFE_SMALL_INTEGER); + let matches = this._findMatches(findScopes, replacePattern.hasReplacementPatterns || this._state.preserveCase, Constants.MAX_SAFE_SMALL_INTEGER); let replaceStrings: string[] = []; for (let i = 0, len = matches.length; i < len; i++) { @@ -523,10 +536,10 @@ export class FindModelBoundToEditorModel { return; } - let findScope = this._decorations.getFindScope(); + let findScopes = this._decorations.getFindScopes(); // Get all the ranges (even more than the highlighted ones) - let matches = this._findMatches(findScope, false, Constants.MAX_SAFE_SMALL_INTEGER); + let matches = this._findMatches(findScopes, false, Constants.MAX_SAFE_SMALL_INTEGER); let selections = matches.map(m => new Selection(m.range.startLineNumber, m.range.startColumn, m.range.endLineNumber, m.range.endColumn)); // If one of the ranges is the editor selection, then maintain it as primary diff --git a/src/vs/editor/contrib/find/findState.ts b/src/vs/editor/contrib/find/findState.ts index dbadf2bb664..0313dd8bc46 100644 --- a/src/vs/editor/contrib/find/findState.ts +++ b/src/vs/editor/contrib/find/findState.ts @@ -46,7 +46,7 @@ export interface INewFindReplaceState { matchCaseOverride?: FindOptionOverride; preserveCase?: boolean; preserveCaseOverride?: FindOptionOverride; - searchScope?: Range | null; + searchScope?: Range[] | null; loop?: boolean; } @@ -73,7 +73,7 @@ export class FindReplaceState extends Disposable { private _matchCaseOverride: FindOptionOverride; private _preserveCase: boolean; private _preserveCaseOverride: FindOptionOverride; - private _searchScope: Range | null; + private _searchScope: Range[] | null; private _matchesPosition: number; private _matchesCount: number; private _currentMatch: Range | null; @@ -94,7 +94,7 @@ export class FindReplaceState extends Disposable { public get actualMatchCase(): boolean { return this._matchCase; } public get actualPreserveCase(): boolean { return this._preserveCase; } - public get searchScope(): Range | null { return this._searchScope; } + public get searchScope(): Range[] | null { return this._searchScope; } public get matchesPosition(): number { return this._matchesPosition; } public get matchesCount(): number { return this._matchesCount; } public get currentMatch(): Range | null { return this._currentMatch; } @@ -238,7 +238,11 @@ export class FindReplaceState extends Disposable { this._preserveCase = newState.preserveCase; } if (typeof newState.searchScope !== 'undefined') { - if (!Range.equalsRange(this._searchScope, newState.searchScope)) { + if (!newState.searchScope?.every((newSearchScope) => { + return this._searchScope?.some(existingSearchScope => { + return !Range.equalsRange(existingSearchScope, newSearchScope); + }); + })) { this._searchScope = newState.searchScope; changeEvent.searchScope = true; somethingChanged = true; diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 6a1c15acbea..8dce48f614f 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -803,16 +803,26 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } if (this._toggleSelectionFind.checked) { - let selection = this._codeEditor.getSelection(); - if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { - selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1)); - } - const currentMatch = this._state.currentMatch; - if (selection.startLineNumber !== selection.endLineNumber) { - if (!Range.equalsRange(selection, currentMatch)) { - // Reseed find scope - this._state.change({ searchScope: selection }, true); + let selections = this._codeEditor.getSelections(); + + selections.map(selection => { + if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { + selection = selection.setEndPosition( + selection.endLineNumber - 1, + this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1) + ); } + const currentMatch = this._state.currentMatch; + if (selection.startLineNumber !== selection.endLineNumber) { + if (!Range.equalsRange(selection, currentMatch)) { + return selection; + } + } + return null; + }).filter(element => !!element); + + if (selections.length) { + this._state.change({ searchScope: selections as Range[] }, true); } } } @@ -1027,12 +1037,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._register(this._toggleSelectionFind.onChange(() => { if (this._toggleSelectionFind.checked) { if (this._codeEditor.hasModel()) { - let selection = this._codeEditor.getSelection(); - if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { - selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1)); - } - if (!selection.isEmpty()) { - this._state.change({ searchScope: selection }, true); + let selections = this._codeEditor.getSelections(); + selections.map(selection => { + if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { + selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1)); + } + if (!selection.isEmpty()) { + return selection; + } + return null; + }).filter(element => !!element); + + if (selections.length) { + this._state.change({ searchScope: selections as Range[] }, true); } } } else { diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 44895a2d658..b440d42671a 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -309,10 +309,10 @@ suite('FindController', () => { assert.equal(findController.getState().searchScope, null); findController.getState().change({ - searchScope: new Range(1, 1, 1, 5) + searchScope: [new Range(1, 1, 1, 5)] }, false); - assert.deepEqual(findController.getState().searchScope, new Range(1, 1, 1, 5)); + assert.deepEqual(findController.getState().searchScope, [new Range(1, 1, 1, 5)]); findController.closeFindWidget(); assert.equal(findController.getState().searchScope, null); @@ -523,10 +523,8 @@ suite('FindController query options persistence', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor) => { // clipboardState = ''; - editor.setSelection(new Range(1, 1, 2, 1)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - - findController.start({ + const findConfig = { forceRevealReplace: false, seedSearchStringFromSelection: false, seedSearchStringFromGlobalClipboard: false, @@ -534,9 +532,17 @@ suite('FindController query options persistence', () => { shouldAnimate: false, updateSearchScope: true, loop: true - }); + }; - assert.deepEqual(findController.getState().searchScope, new Selection(1, 1, 2, 1)); + editor.setSelection(new Range(1, 1, 2, 1)); + findController.start(findConfig); + assert.deepEqual(findController.getState().searchScope, [new Selection(1, 1, 2, 1)]); + + findController.closeFindWidget(); + + editor.setSelections([new Selection(1, 1, 2, 1), new Selection(2, 1, 2, 5)]); + findController.start(findConfig); + assert.deepEqual(findController.getState().searchScope, [new Selection(1, 1, 2, 1), new Selection(2, 1, 2, 5)]); }); }); @@ -584,7 +590,7 @@ suite('FindController query options persistence', () => { loop: true }); - assert.deepEqual(findController.getState().searchScope, new Selection(1, 2, 1, 3)); + assert.deepEqual(findController.getState().searchScope, [new Selection(1, 2, 1, 3)]); }); }); @@ -609,7 +615,7 @@ suite('FindController query options persistence', () => { loop: true }); - assert.deepEqual(findController.getState().searchScope, new Selection(1, 6, 2, 1)); + assert.deepEqual(findController.getState().searchScope, [new Selection(1, 6, 2, 1)]); }); }); }); diff --git a/src/vs/editor/contrib/find/test/findModel.test.ts b/src/vs/editor/contrib/find/test/findModel.test.ts index 1a515fdddcc..218c52192a8 100644 --- a/src/vs/editor/contrib/find/test/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/findModel.test.ts @@ -210,7 +210,7 @@ suite('FindModel', () => { ); // simulate adding a search scope - findState.change({ searchScope: new Range(8, 1, 10, 1) }, true); + findState.change({ searchScope: [new Range(8, 1, 10, 1)] }, true); assertFindState( editor, [8, 14, 8, 19], @@ -443,7 +443,7 @@ suite('FindModel', () => { findTest('find model next stays in scope', (editor) => { let findState = new FindReplaceState(); - findState.change({ searchString: 'hello', wholeWord: true, searchScope: new Range(7, 1, 9, 1) }, false); + findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 9, 1)] }, false); let findModel = new FindModelBoundToEditorModel(editor, findState); assertFindState( @@ -493,6 +493,79 @@ suite('FindModel', () => { findState.dispose(); }); + findTest('multi-selection find model next stays in scope', (editor) => { + let findState = new FindReplaceState(); + findState.change({ searchString: 'hello', matchCase: true, wholeWord: false, searchScope: [new Range(6, 1, 7, 38), new Range(9, 3, 9, 38)] }, false); + let findModel = new FindModelBoundToEditorModel(editor, findState); + + assertFindState( + editor, + [1, 1, 1, 1], + null, + [ + [6, 14, 6, 19], + // `matchCase: false` would + // find this match as well: + // [6, 27, 6, 32], + [7, 14, 7, 19], + // `wholeWord: true` would + // exclude this match: + [9, 14, 9, 19], + ] + ); + + findModel.moveToNextMatch(); + assertFindState( + editor, + [6, 14, 6, 19], + [6, 14, 6, 19], + [ + [6, 14, 6, 19], + [7, 14, 7, 19], + [9, 14, 9, 19], + ] + ); + + findModel.moveToNextMatch(); + assertFindState( + editor, + [7, 14, 7, 19], + [7, 14, 7, 19], + [ + [6, 14, 6, 19], + [7, 14, 7, 19], + [9, 14, 9, 19], + ] + ); + + findModel.moveToNextMatch(); + assertFindState( + editor, + [9, 14, 9, 19], + [9, 14, 9, 19], + [ + [6, 14, 6, 19], + [7, 14, 7, 19], + [9, 14, 9, 19], + ] + ); + + findModel.moveToNextMatch(); + assertFindState( + editor, + [6, 14, 6, 19], + [6, 14, 6, 19], + [ + [6, 14, 6, 19], + [7, 14, 7, 19], + [9, 14, 9, 19], + ] + ); + + findModel.dispose(); + findState.dispose(); + }); + findTest('find model prev', (editor) => { let findState = new FindReplaceState(); findState.change({ searchString: 'hello', wholeWord: true }, false); @@ -581,7 +654,7 @@ suite('FindModel', () => { findTest('find model prev stays in scope', (editor) => { let findState = new FindReplaceState(); - findState.change({ searchString: 'hello', wholeWord: true, searchScope: new Range(7, 1, 9, 1) }, false); + findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 9, 1)] }, false); let findModel = new FindModelBoundToEditorModel(editor, findState); assertFindState( @@ -2073,7 +2146,7 @@ suite('FindModel', () => { findTest('issue #27083. search scope works even if it is a single line', (editor) => { let findState = new FindReplaceState(); - findState.change({ searchString: 'hello', wholeWord: true, searchScope: new Range(7, 1, 8, 1) }, false); + findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 8, 1)] }, false); let findModel = new FindModelBoundToEditorModel(editor, findState); assertFindState( diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index cdb7d0c6190..4bf81a1ec8a 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -601,13 +601,15 @@ export class MultiCursorSelectionController extends Disposable implements IEdito } if (findState.searchScope) { - const state = findState.searchScope; + const states = findState.searchScope; let inSelection: FindMatch[] | null = []; - for (let i = 0; i < matches.length; i++) { - if (matches[i].range.endLineNumber <= state.endLineNumber && matches[i].range.startLineNumber >= state.startLineNumber) { - inSelection.push(matches[i]); - } - } + matches.forEach((match) => { + states.forEach((state) => { + if (match.range.endLineNumber <= state.endLineNumber && match.range.startLineNumber >= state.startLineNumber) { + inSelection!.push(match); + } + }); + }); matches = inSelection; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e4e0752901d..987b9cd0533 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1758,7 +1758,7 @@ declare namespace monaco.editor { /** * Search the model. * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchScope Limit the searching to only search inside this range. + * @param searchScope Limit the searching to only search inside these ranges. * @param isRegex Used to indicate that `searchString` is a regular expression. * @param matchCase Force the matching to match lower/upper case exactly. * @param wordSeparators Force the matching to match entire words only. Pass null otherwise. @@ -1766,7 +1766,7 @@ declare namespace monaco.editor { * @param limitResultCount Limit the number of results * @return The ranges where the matches are. It is empty if no matches have been found. */ - findMatches(searchString: string, searchScope: IRange, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount?: number): FindMatch[]; + findMatches(searchString: string, searchScope: IRange | IRange[], isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount?: number): FindMatch[]; /** * Search the model for the next match. Loops to the beginning of the model if needed. * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. From b3fba742c5846997eb7ef1f36a1860588eff681a Mon Sep 17 00:00:00 2001 From: daybrush Date: Sat, 4 Jul 2020 05:04:06 +0900 Subject: [PATCH 007/695] Fix cut & copy for iPad --- src/vs/editor/browser/controller/textAreaHandler.ts | 7 ------- src/vs/editor/test/browser/controller/imeTester.ts | 7 ------- 2 files changed, 14 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 9eee0dfe476..220c6c440ea 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -178,14 +178,7 @@ export class TextAreaHandler extends ViewPart { mode }; }, - getScreenReaderContent: (currentState: TextAreaState): TextAreaState => { - - if (browser.isIPad) { - // Do not place anything in the textarea for the iPad - return TextAreaState.EMPTY; - } - if (this._accessibilitySupport === AccessibilitySupport.Disabled) { // We know for a fact that a screen reader is not attached // On OSX, we write the character before the cursor to allow for "long-press" composition diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 1668233bf9d..508aa9fc833 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import { createFastDomNode } from 'vs/base/browser/fastDomNode'; import { ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; import { ISimpleModel, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; @@ -96,12 +95,6 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string }; }, getScreenReaderContent: (currentState: TextAreaState): TextAreaState => { - - if (browser.isIPad) { - // Do not place anything in the textarea for the iPad - return TextAreaState.EMPTY; - } - const selection = new Range(1, 1 + cursorOffset, 1, 1 + cursorOffset + cursorLength); return PagedScreenReaderStrategy.fromEditorSelection(currentState, model, selection, 10, true); From 00dea1c4ad670898678506a151e637d2775a103d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 17 Jul 2020 11:02:08 +0200 Subject: [PATCH 008/695] create "real" TextDocument when opening a notebook or when adding cells, don't create them again when the renderer makes models --- .../src/notebook.test.ts | 26 ++ src/vs/base/common/network.ts | 2 + .../browser/mainThreadDocumentsAndEditors.ts | 7 +- .../workbench/api/common/extHostDocuments.ts | 4 +- .../workbench/api/common/extHostNotebook.ts | 74 +++--- .../contrib/notebook/common/notebookCommon.ts | 2 +- .../test/browser/api/extHostNotebook.test.ts | 227 +++++++++--------- .../api/extHostNotebookConcatDocument.test.ts | 11 - 8 files changed, 188 insertions(+), 165 deletions(-) diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index 788585e3d4d..852e9c97d17 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -93,6 +93,32 @@ suite('Notebook API tests', () => { await firstDocumentClose; }); + test('notebook open/close, all cell-documents are ready', async function () { + const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + + const p = new Promise((resolve, reject) => { + once(vscode.notebook.onDidOpenNotebookDocument)(notebook => { + try { + for (let cell of notebook.cells) { + const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === cell.uri.toString()); + assert.ok(doc); + assert.strictEqual(doc === cell.document, true); + assert.strictEqual(doc?.languageId, cell.language); + assert.strictEqual(doc?.isDirty, false); + assert.strictEqual(doc?.isClosed, false); + } + resolve(); + } catch (err) { + reject(err); + } + }); + }); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await p; + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + test('shared document in notebook editors', async function () { const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); let counter = 0; diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 1286c5117a4..4b6aebc1646 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -58,6 +58,8 @@ export namespace Schemas { export const vscodeNotebook = 'vscode-notebook'; + export const vscodeNotebookCell = 'vscode-notebook-cell'; + export const vscodeSettings = 'vscode-settings'; export const webviewPanel = 'webview-panel'; diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 5f69d9ee9bc..537fc580a31 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -30,6 +30,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { Schemas } from 'vs/base/common/network'; namespace delta { @@ -399,8 +400,12 @@ export class MainThreadDocumentsAndEditors { extHostDelta.removedEditors = removedEditors; } if (delta.addedDocuments.length > 0) { + // notebook cell documents get sync'd by the notebook open logic + // and therefore we don't send them another time now. empty = false; - extHostDelta.addedDocuments = delta.addedDocuments.map(m => this._toModelAddData(m)); + extHostDelta.addedDocuments = delta.addedDocuments + .filter(m => m.uri.scheme !== Schemas.vscodeNotebookCell) + .map(m => this._toModelAddData(m)); } if (delta.addedEditors.length > 0) { empty = false; diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index af4a3f9de52..ab8d770765b 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -69,8 +69,8 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { public getDocument(resource: vscode.Uri): vscode.TextDocument { const data = this.getDocumentData(resource); - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); + if (!data?.document) { + throw new Error(`Unable to retrieve document from URI '${resource}'`); } return data.document; } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index c55f9218edc..959d8c0d57a 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -11,14 +11,12 @@ import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { CellKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta, IModelAddedData } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { CellEditType, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo, IRawOutput, CellOutputKind, IProcessedOutput, INotebookKernelInfoDto2, IMainCellDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; -import { NotImplementedProxy } from 'vs/base/common/types'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; @@ -57,28 +55,19 @@ interface INotebookEventEmitter { const addIdToOutput = (output: IRawOutput, id = UUID.generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich ? ({ ...output, outputId: id }) : output; -class DettachedCellDocumentData extends ExtHostDocumentData { - - private static readonly _fakeProxy = new class extends NotImplementedProxy('document') { - $trySaveDocument() { - return Promise.reject('Cell-document cannot be saved'); - } - }; - - constructor(cell: IMainCellDto) { - super(DettachedCellDocumentData._fakeProxy, - URI.revive(cell.uri), - cell.source, - cell.eol, - cell.language, - 0, - false - ); - } -} - export class ExtHostCell extends Disposable implements vscode.NotebookCell { + public static asModelAddData(cell: IMainCellDto): IModelAddedData { + return { + EOL: cell.eol, + lines: cell.source, + modeId: cell.language, + uri: cell.uri, + isDirty: false, + versionId: 1 + }; + } + private _onDidChangeOutputs = new Emitter[]>(); readonly onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; @@ -92,13 +81,6 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { readonly uri: URI; readonly cellKind: CellKind; - // todo@jrieken this is a little fish because we have - // vscode.TextDocument for which we never fired an onDidOpen - // event and which doesn't appear in the list of documents. - // this will change once the "real" document comes along. We - // should come up with a better approach here... - readonly defaultDocument: DettachedCellDocumentData; - constructor( private _proxy: MainThreadNotebookShape, readonly notebook: ExtHostNotebookDocument, @@ -110,7 +92,6 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { this.handle = cell.handle; this.uri = URI.revive(cell.uri); this.cellKind = cell.cellKind; - this.defaultDocument = new DettachedCellDocumentData(cell); this._outputs = cell.outputs; for (const output of this._outputs) { @@ -126,7 +107,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { } get document(): vscode.TextDocument { - return this._extHostDocument.getDocument(this.uri)?.document ?? this.defaultDocument.document; + return this._extHostDocument.getDocument(this.uri)!.document; } get language(): string { @@ -368,7 +349,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo return; } - let contentChangeEvents: vscode.NotebookCellsChangeData[] = []; + const contentChangeEvents: vscode.NotebookCellsChangeData[] = []; + const addedCellDocuments: IModelAddedData[] = []; splices.reverse().forEach(splice => { let cellDtos = splice[2]; @@ -376,6 +358,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell); + if (!initialization) { + addedCellDocuments.push(ExtHostCell.asModelAddData(cell)); + } + if (!this._cellDisposableMapping.has(extCell.handle)) { this._cellDisposableMapping.set(extCell.handle, new DisposableStore()); } @@ -392,21 +378,22 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo for (let j = splice[0]; j < splice[0] + splice[1]; j++) { this._cellDisposableMapping.get(this.cells[j].handle)?.dispose(); this._cellDisposableMapping.delete(this.cells[j].handle); - } const deletedItems = this.cells.splice(splice[0], splice[1], ...newCells); - const event: vscode.NotebookCellsChangeData = { + contentChangeEvents.push({ start: splice[0], deletedCount: splice[1], deletedItems, items: newCells - }; - - contentChangeEvents.push(event); + }); }); + if (addedCellDocuments) { + this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments }); + } + if (!initialization) { this._emitter.emitModelChange({ document: this, @@ -456,7 +443,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo private $changeCellLanguage(index: number, language: string): void { const cell = this.cells[index]; - cell.defaultDocument._acceptLanguageId(language); const event: vscode.NotebookCellLanguageChangeEvent = { document: this, cell, language }; this._emitter.emitCellLanguageChange(event); } @@ -1427,6 +1413,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } if (delta.addedDocuments) { + + const addedCellDocuments: IModelAddedData[] = []; + delta.addedDocuments.forEach(modelData => { const revivedUri = URI.revive(modelData.uri); const revivedUriStr = revivedUri.toString(); @@ -1470,6 +1459,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ]] }); + // add cell document as vscode.TextDocument + addedCellDocuments.push(...modelData.cells.map(ExtHostCell.asModelAddData)); + this._documents.get(revivedUriStr)?.dispose(); this._documents.set(revivedUriStr, document); @@ -1480,6 +1472,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } + this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ + addedDocuments: addedCellDocuments + }); + const document = this._documents.get(revivedUriStr)!; this._onDidOpenNotebookDocument.fire(document); }); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index e7063754ffe..5582306d02f 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -397,7 +397,7 @@ export interface NotebookDataDto { export namespace CellUri { - export const scheme = 'vscode-notebook-cell'; + export const scheme = Schemas.vscodeNotebookCell; const _regex = /^\d{7,}/; export function generate(notebook: URI, handle: number): URI { diff --git a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts index 4f4d4967242..c793c27a5fa 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts @@ -4,148 +4,153 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import * as vscode from 'vscode'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { NullLogService } from 'vs/platform/log/common/log'; import { mock } from 'vs/base/test/common/mock'; -import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostNotebookDocument, ExtHostCell } from 'vs/workbench/api/common/extHostNotebook'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; +import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { isEqual } from 'vs/base/common/resources'; + +suite('NotebookCell#Document', function () { -suite('NotebookCell', function () { let rpcProtocol: TestRPCProtocol; + let notebook: ExtHostNotebookDocument; let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors; - + let extHostDocuments: ExtHostDocuments; + let extHostNotebooks: ExtHostNotebookController; + const notebookUri = URI.parse('test:///notebook.file'); const disposables = new DisposableStore(); - const fakeNotebookProxy = new class extends mock() { }; - const fakeNotebook = new class extends mock() { }; setup(async function () { disposables.clear(); + rpcProtocol = new TestRPCProtocol(); + rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock() { + $registerCommand() { } + }); + rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { + async $registerNotebookProvider() { } + async $unregisterNotebookProvider() { } + }); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); - }); - - test('Document is real', function () { - - const dto = { - cellKind: CellKind.Code, - eol: '\n', - source: ['aaaa', 'bbbb', 'cccc'], - handle: 0, - language: 'fooLang', - outputs: [], - uri: URI.parse('test:/path') - }; - const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); - - assert.ok(cell.document); - assert.strictEqual(cell.document.version, 0); - assert.strictEqual(cell.document.languageId, dto.language); - assert.strictEqual(cell.document.uri.toString(), dto.uri.toString()); - assert.strictEqual(cell.uri.toString(), dto.uri.toString()); - }); - - - test('Document is uses actual document when possible', function () { - - const dto = { - cellKind: CellKind.Code, - eol: '\n', - source: ['aaaa', 'bbbb', 'cccc'], - handle: 0, - language: 'fooLang', - outputs: [], - uri: URI.parse('test:/path') - }; - const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); - - // this is the "default document" which is used when the real - // document isn't open - const documentNow = cell.document; - - extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ + extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); + extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }); + let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock() { + // async openNotebook() { } + }); + await extHostNotebooks.$acceptDocumentAndEditorsDelta({ addedDocuments: [{ - isDirty: false, - versionId: 12, - modeId: dto.language, - uri: dto.uri, - lines: dto.source, - EOL: dto.eol + handle: 0, + uri: notebookUri, + viewType: 'test', + versionId: 0, + cells: [{ + handle: 0, + uri: CellUri.generate(notebookUri, 0), + source: ['### Heading'], + eol: '\n', + language: 'markdown', + cellKind: CellKind.Markdown, + outputs: [], + }, { + handle: 1, + uri: CellUri.generate(notebookUri, 1), + source: ['console.log("aaa")', 'console.log("bbb")'], + eol: '\n', + language: 'javascript', + cellKind: CellKind.Code, + outputs: [], + }], + }], + addedEditors: [{ + documentUri: notebookUri, + id: '_notebook_editor_0', + selections: [0] }] }); + await extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' }); - // the real document - assert.ok(documentNow !== cell.document); - assert.strictEqual(cell.document.languageId, dto.language); - assert.strictEqual(cell.document.uri.toString(), dto.uri.toString()); - assert.strictEqual(cell.uri.toString(), dto.uri.toString()); + notebook = extHostNotebooks.notebookDocuments[0]!; - // back to "default document" - extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: [dto.uri] }); - assert.ok(documentNow === cell.document); - }); - - test('Document can change language (1/2)', function () { - - const dto = { - cellKind: CellKind.Code, - eol: '\n', - source: ['aaaa', 'bbbb', 'cccc'], - handle: 0, - language: 'fooLang', - outputs: [], - uri: URI.parse('test:/path') - }; - const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); - - assert.strictEqual(cell.document.languageId, dto.language); - cell.defaultDocument._acceptLanguageId('barLang'); - assert.strictEqual(cell.document.languageId, 'barLang'); + disposables.add(reg); + disposables.add(notebook); + disposables.add(extHostDocuments); }); - test('Document can change language (1/2)', function () { + test('cell document is vscode.TextDocument', async function () { + assert.strictEqual(notebook.cells.length, 2); - const dto = { - cellKind: CellKind.Code, - eol: '\n', - source: ['aaaa', 'bbbb', 'cccc'], - handle: 0, - language: 'fooLang', - outputs: [], - uri: URI.parse('test:/path') - }; + const [c1, c2] = notebook.cells; + const d1 = extHostDocuments.getDocument(c1.uri); - extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ - addedDocuments: [{ - isDirty: false, - versionId: 12, - modeId: dto.language, - uri: dto.uri, - lines: dto.source, - EOL: dto.eol - }] + assert.ok(d1); + assert.equal(d1.languageId, c1.language); + + const d2 = extHostDocuments.getDocument(c2.uri); + assert.ok(d2); + assert.equal(d2.languageId, c2.language); + }); + + test('cell document is vscode.TextDocument after changing it', async function () { + + const p = new Promise((resolve, reject) => { + extHostNotebooks.onDidChangeNotebookCells(e => { + try { + assert.strictEqual(e.changes.length, 1); + assert.strictEqual(e.changes[0].items.length, 2); + + const [first, second] = e.changes[0].items; + + const doc1 = extHostDocuments.getAllDocumentData().find(data => isEqual(data.document.uri, first.uri)); + assert.ok(doc1); + assert.strictEqual(doc1?.document === first.document, true); + + const doc2 = extHostDocuments.getAllDocumentData().find(data => isEqual(data.document.uri, second.uri)); + assert.ok(doc2); + assert.strictEqual(doc2?.document === second.document, true); + + resolve(); + + } catch (err) { + reject(err); + } + }); }); - const extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); + extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, + versionId: notebook.versionId + 1, + changes: [[0, 0, [{ + handle: 2, + uri: CellUri.generate(notebookUri, 2), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 3, + uri: CellUri.generate(notebookUri, 3), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); - const cell = new ExtHostCell(fakeNotebookProxy, fakeNotebook, extHostDocumentsAndEditors, dto); + await p; - // a real document already exists and therefore - // the "default document" doesn't count - - assert.strictEqual(cell.document.languageId, dto.language); - cell.defaultDocument._acceptLanguageId('barLang'); - assert.strictEqual(cell.document.languageId, dto.language); - - extHostDocuments.$acceptModelModeChanged(dto.uri, dto.language, 'barLang'); - assert.strictEqual(cell.document.languageId, 'barLang'); }); - }); diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 3fcaa6da684..c55d9dede7c 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -292,17 +292,6 @@ suite('NotebookConcatDocument', function () { let cell1End = doc.offsetAt(new Position(2, 12)); assert.equal(doc.positionAt(cell1End).isEqual(new Position(2, 12)), true); - extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ - addedDocuments: [{ - uri: notebook.cells[0].uri, - versionId: 1, - lines: ['Hello', 'World', 'Hello World!'], - EOL: '\n', - modeId: '', - isDirty: false - }] - }); - extHostDocuments.$acceptModelChanged(notebook.cells[0].uri, { versionId: 0, eol: '\n', From 04eef0779dedb100f335bdad3c54459b4d70fbcf Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 17 Jul 2020 11:16:38 +0200 Subject: [PATCH 009/695] add test that asserts cell document open are fired when the notebook is ready, not before --- .../src/notebook.test.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index 852e9c97d17..5bd7a32ab2a 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -119,6 +119,27 @@ suite('Notebook API tests', () => { await vscode.commands.executeCommand('workbench.action.closeAllEditors'); }); + test('notebook open/close, notebook ready when cell-document open event is fired', async function () { + const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + let didHappen = false; + const p = getEventOncePromise(vscode.workspace.onDidOpenTextDocument).then(doc => { + if (doc.uri.scheme !== 'vscode-notebook-cell') { + return; + } + const notebook = vscode.notebook.notebookDocuments.find(notebook => { + const cell = notebook.cells.find(cell => cell.document === doc); + return Boolean(cell); + }); + assert.ok(notebook, `notebook for cell ${doc.uri} NOT found`); + didHappen = true; + }); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await p; + assert.strictEqual(didHappen, true); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + test('shared document in notebook editors', async function () { const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); let counter = 0; From b3f090d3296471d8d2e16601f7ceeb43216da3e3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 17 Jul 2020 11:18:35 +0200 Subject: [PATCH 010/695] use getEventOncePromise-util --- .../src/notebook.test.ts | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index 5bd7a32ab2a..55862568d13 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -96,22 +96,15 @@ suite('Notebook API tests', () => { test('notebook open/close, all cell-documents are ready', async function () { const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); - const p = new Promise((resolve, reject) => { - once(vscode.notebook.onDidOpenNotebookDocument)(notebook => { - try { - for (let cell of notebook.cells) { - const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === cell.uri.toString()); - assert.ok(doc); - assert.strictEqual(doc === cell.document, true); - assert.strictEqual(doc?.languageId, cell.language); - assert.strictEqual(doc?.isDirty, false); - assert.strictEqual(doc?.isClosed, false); - } - resolve(); - } catch (err) { - reject(err); - } - }); + const p = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument).then(notebook => { + for (let cell of notebook.cells) { + const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === cell.uri.toString()); + assert.ok(doc); + assert.strictEqual(doc === cell.document, true); + assert.strictEqual(doc?.languageId, cell.language); + assert.strictEqual(doc?.isDirty, false); + assert.strictEqual(doc?.isClosed, false); + } }); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); From b2666580914d34630f0b0e668d7dac008d1f823b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 17 Jul 2020 14:52:25 +0200 Subject: [PATCH 011/695] remove document that only the notebook did open --- src/vs/workbench/api/common/extHostNotebook.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 959d8c0d57a..f973ad18c72 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { readonly } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; @@ -64,7 +64,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { modeId: cell.language, uri: cell.uri, isDirty: false, - versionId: 1 + versionId: -1 }; } @@ -320,7 +320,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo dispose() { this._disposed = true; super.dispose(); - this._cellDisposableMapping.forEach(cell => cell.dispose()); + dispose(this._cellDisposableMapping.values()); } get fileName() { return this.uri.fsPath; } @@ -1397,8 +1397,19 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN let document = this._documents.get(revivedUriStr); if (document) { + + // remove all documents that have not been opened by the renderer + // and have not yet been closed + const removedCellDocuments: URI[] = []; + for (let cell of document.cells) { + if (this._documentsAndEditors.getDocument(cell.uri)?.version === -1) { + removedCellDocuments.push(cell.uri); + } + } + document.dispose(); this._documents.delete(revivedUriStr); + this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: removedCellDocuments }); this._onDidCloseNotebookDocument.fire(document); } From 39774a2d8d416c3996ea13107c5744be620bc119 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 17 Jul 2020 18:14:00 +0200 Subject: [PATCH 012/695] some more unit testing --- .../test/browser/api/extHostNotebook.test.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts index c793c27a5fa..ccf9dbe7347 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts @@ -96,10 +96,31 @@ suite('NotebookCell#Document', function () { assert.ok(d1); assert.equal(d1.languageId, c1.language); + assert.equal(d1.version, -1); // we use -1 as signal that the document was created on the ext-host side const d2 = extHostDocuments.getDocument(c2.uri); assert.ok(d2); assert.equal(d2.languageId, c2.language); + assert.equal(d2.version, -1); + }); + + test('cell document goes when notebook closes', async function () { + const cellUris: string[] = []; + for (let cell of notebook.cells) { + assert.ok(extHostDocuments.getDocument(cell.uri)); + cellUris.push(cell.uri.toString()); + } + + const removedCellUris: string[] = []; + const reg = extHostDocuments.onDidRemoveDocument(doc => { + removedCellUris.push(doc.uri.toString()); + }); + + await extHostNotebooks.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] }); + reg.dispose(); + + assert.strictEqual(removedCellUris.length, 2); + assert.deepStrictEqual(removedCellUris.sort(), cellUris.sort()); }); test('cell document is vscode.TextDocument after changing it', async function () { From 660ffebf0a886f91e930121275b2b9cced300363 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 17 Jul 2020 18:23:16 +0200 Subject: [PATCH 013/695] always send model add data and check on the extension if this is a notebook-cell special case this sends the twice but gurantees that the renderer owns the model --- .../api/browser/mainThreadDocumentsAndEditors.ts | 7 +------ .../workbench/api/common/extHostDocumentsAndEditors.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 537fc580a31..5f69d9ee9bc 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -30,7 +30,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { Schemas } from 'vs/base/common/network'; namespace delta { @@ -400,12 +399,8 @@ export class MainThreadDocumentsAndEditors { extHostDelta.removedEditors = removedEditors; } if (delta.addedDocuments.length > 0) { - // notebook cell documents get sync'd by the notebook open logic - // and therefore we don't send them another time now. empty = false; - extHostDelta.addedDocuments = delta.addedDocuments - .filter(m => m.uri.scheme !== Schemas.vscodeNotebookCell) - .map(m => this._toModelAddData(m)); + extHostDelta.addedDocuments = delta.addedDocuments.map(m => this._toModelAddData(m)); } if (delta.addedEditors.length > 0) { empty = false; diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index bf3919a0841..63fc98becaa 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -15,6 +15,7 @@ import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { ILogService } from 'vs/platform/log/common/log'; import { ResourceMap } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { @@ -60,7 +61,14 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha if (delta.addedDocuments) { for (const data of delta.addedDocuments) { const resource = URI.revive(data.uri); - assert.ok(!this._documents.has(resource), `document '${resource} already exists!'`); + const existingDocumentData = this._documents.get(resource); + if (existingDocumentData) { + if (resource.scheme !== Schemas.vscodeNotebookCell) { + throw new Error(`document '${resource} already exists!'`); + } + existingDocumentData.onEvents({ changes: [], versionId: data.versionId, eol: data.EOL }); + continue; + } const documentData = new ExtHostDocumentData( this._extHostRpc.getProxy(MainContext.MainThreadDocuments), From 4cdb2dfa3cf45ab92bf8c6d93608685394d3ec46 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Thu, 23 Jul 2020 09:28:50 -0700 Subject: [PATCH 014/695] TAS-based experiment service (#103177) * new experimentation service based on tas-client * fixes for exp service * add event classifications * leverage product.json --- package.json | 1 + .../standalone/browser/simpleServices.ts | 3 + .../platform/product/common/productService.ts | 7 + src/vs/platform/telemetry/common/telemetry.ts | 2 + .../telemetry/common/telemetryService.ts | 8 + .../telemetry/common/telemetryUtils.ts | 1 + .../experiment/common/experimentService.ts | 13 ++ .../electron-browser/experimentService.ts | 209 ++++++++++++++++++ .../telemetry/browser/telemetryService.ts | 4 + .../electron-browser/telemetryService.ts | 4 + .../textsearch.perf.integrationTest.ts | 3 + src/vs/workbench/workbench.desktop.main.ts | 1 + yarn.lock | 23 +- 13 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/services/experiment/common/experimentService.ts create mode 100644 src/vs/workbench/services/experiment/electron-browser/experimentService.ts diff --git a/package.json b/package.json index 63036471803..70e50d71d95 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "semver-umd": "^5.5.7", "spdlog": "^0.11.1", "sudo-prompt": "9.1.1", + "tas-client": "^0.0.950", "v8-inspect-profiler": "^0.0.20", "vscode-nsfw": "1.2.8", "vscode-oniguruma": "1.3.1", diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index df5c7eb96b5..9051fb2cc78 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -552,6 +552,9 @@ export class StandaloneTelemetryService implements ITelemetryService { public setEnabled(value: boolean): void { } + public setExperimentProperty(name: string, value: string): void { + } + public publicLog(eventName: string, data?: any): Promise { return Promise.resolve(undefined); } diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 188786627ce..b119d2b8048 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -49,6 +49,13 @@ export interface IProductConfiguration { readonly settingsSearchBuildId?: number; readonly settingsSearchUrl?: string; + readonly tasConfig?: { + endpoint: string; + telemetryEventName: string; + featuresTelemetryPropertyName: string; + assignmentContextTelemetryPropertyName: string; + }; + readonly experimentsUrl?: string; readonly extensionsGallery?: { diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index e0a9a30e55c..1acf9259110 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -46,6 +46,8 @@ export interface ITelemetryService { getTelemetryInfo(): Promise; + setExperimentProperty(name: string, value: string): void; + isOptedIn: boolean; } diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 49c4d59d5d5..1e1c6fcb583 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -31,6 +31,7 @@ export class TelemetryService implements ITelemetryService { private _appender: ITelemetryAppender; private _commonProperties: Promise<{ [name: string]: any; }>; + private _experimentProperties: { [name: string]: string } = {}; private _piiPaths: string[]; private _userOptIn: boolean; private _enabled: boolean; @@ -79,6 +80,10 @@ export class TelemetryService implements ITelemetryService { } } + setExperimentProperty(name: string, value: string): void { + this._experimentProperties[name] = value; + } + setEnabled(value: boolean): void { this._enabled = value; } @@ -119,6 +124,9 @@ export class TelemetryService implements ITelemetryService { // (first) add common properties data = mixin(data, values); + // (next) add experiment properties + data = mixin(data, this._experimentProperties); + // (last) remove all PII from data data = cloneAndChange(data, value => { if (typeof value === 'string') { diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index ae0a2110009..392c3ad8ea8 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -28,6 +28,7 @@ export const NullTelemetryService = new class implements ITelemetryService { return this.publicLogError(eventName, data as ITelemetryData); } + setExperimentProperty() { } setEnabled() { } isOptedIn = true; getTelemetryInfo(): Promise { diff --git a/src/vs/workbench/services/experiment/common/experimentService.ts b/src/vs/workbench/services/experiment/common/experimentService.ts new file mode 100644 index 00000000000..758c525e263 --- /dev/null +++ b/src/vs/workbench/services/experiment/common/experimentService.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ITASExperimentService = createDecorator('TASExperimentService'); + +export interface ITASExperimentService { + readonly _serviceBrand: undefined; + getTreatment(name: string): Promise; +} diff --git a/src/vs/workbench/services/experiment/electron-browser/experimentService.ts b/src/vs/workbench/services/experiment/electron-browser/experimentService.ts new file mode 100644 index 00000000000..734c61a142b --- /dev/null +++ b/src/vs/workbench/services/experiment/electron-browser/experimentService.ts @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/base/common/platform'; +import { IKeyValueStorage, IExperimentationTelemetry, IExperimentationFilterProvider, ExperimentationService as TASClient } from 'tas-client'; +import { MementoObject, Memento } from 'vs/workbench/common/memento'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ITelemetryData } from 'vs/base/common/actions'; +import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +const storageKey = 'VSCode.ABExp.FeatureData'; +const refetchInterval = 1000 * 60 * 30; // By default it's set up to 30 minutes. + +class MementoKeyValueStorage implements IKeyValueStorage { + constructor(private mementoObj: MementoObject) { } + + async getValue(key: string, defaultValue?: T | undefined): Promise { + const value = await this.mementoObj[key]; + return value || defaultValue; + } + + setValue(key: string, value: T): void { + this.mementoObj[key] = value; + } +} + +class ExperimentServiceTelemetry implements IExperimentationTelemetry { + constructor(private telemetryService: ITelemetryService) { } + + // __GDPR__COMMON__ "VSCode.ABExp.Features" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + // __GDPR__COMMON__ "abexp.assignmentcontext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + setSharedProperty(name: string, value: string): void { + this.telemetryService.setExperimentProperty(name, value); + } + + postEvent(eventName: string, props: Map): void { + const data: ITelemetryData = {}; + for (const [key, value] of props.entries()) { + data[key] = value; + } + + /* __GDPR__ + "query-expfeature" : { + "ABExp.queriedFeature": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog(eventName, data); + } +} + +class ExperimentServiceFilterProvider implements IExperimentationFilterProvider { + constructor( + private version: string, + private appName: string, + private machineId: string, + private targetPopulation: TargetPopulation + ) { } + + getFilterValue(filter: string): string | null { + switch (filter) { + case Filters.ApplicationVersion: + return this.version; // productService.version + case Filters.Build: + return this.appName; // productService.nameLong + case Filters.ClientId: + return this.machineId; + case Filters.Language: + return platform.language; + case Filters.TargetPopulation: + return this.targetPopulation; + default: + return ''; + } + } + + getFilters(): Map { + let filters: Map = new Map(); + let filterValues = Object.values(Filters); + for (let value of filterValues) { + filters.set(value, this.getFilterValue(value)); + } + + return filters; + } +} + +/* +Based upon the official VSCode currently existing filters in the +ExP backend for the VSCode cluster. +https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster +"X-MSEdge-Market": "detection.market", +"X-FD-Corpnet": "detection.corpnet", +"X-VSCode–AppVersion": "appversion", +"X-VSCode-Build": "build", +"X-MSEdge-ClientId": "clientid", +"X-VSCode-TargetPopulation": "targetpopulation", +"X-VSCode-Language": "language" +*/ + +enum Filters { + /** + * The market in which the extension is distributed. + */ + Market = 'X-MSEdge-Market', + + /** + * The corporation network. + */ + CorpNet = 'X-FD-Corpnet', + + /** + * Version of the application which uses experimentation service. + */ + ApplicationVersion = 'X-VSCode-AppVersion', + + /** + * Insiders vs Stable. + */ + Build = 'X-VSCode-Build', + + /** + * Client Id which is used as primary unit for the experimentation. + */ + ClientId = 'X-MSEdge-ClientId', + + /** + * The language in use by VS Code + */ + Language = 'X-VSCode-Language', + + /** + * The target population. + * This is used to separate internal, early preview, GA, etc. + */ + TargetPopulation = 'X-VSCode-TargetPopulation', +} + +enum TargetPopulation { + Team = 'team', + Internal = 'internal', + Insiders = 'insider', + Public = 'public', +} + +export class ExperimentService implements ITASExperimentService { + _serviceBrand: undefined; + private tasClient: Promise | undefined; + private static MEMENTO_ID = 'experiment.service.memento'; + + constructor( + @IProductService private productService: IProductService, + @ITelemetryService private telemetryService: ITelemetryService, + @IStorageService private storageService: IStorageService + ) { + + if (this.productService.tasConfig) { + this.tasClient = this.setupTASClient(); + } + } + + async getTreatment(name: string): Promise { + if (!this.tasClient) { + return undefined; + } + + return (await this.tasClient).getTreatmentVariable('vscode', name); + } + + private async setupTASClient(): Promise { + const telemetryInfo = await this.telemetryService.getTelemetryInfo(); + const targetPopulation = telemetryInfo.msftInternal ? TargetPopulation.Internal : (this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders); + const machineId = telemetryInfo.machineId; + const filterProvider = new ExperimentServiceFilterProvider( + this.productService.version, + this.productService.nameLong, + machineId, + targetPopulation + ); + + const memento = new Memento(ExperimentService.MEMENTO_ID, this.storageService); + const keyValueStorage = new MementoKeyValueStorage(memento.getMemento(StorageScope.GLOBAL)); + + const telemetry = new ExperimentServiceTelemetry(this.telemetryService); + + const tasConfig = this.productService.tasConfig!; + const tasClient = new TASClient({ + filterProviders: [filterProvider], + telemetry: telemetry, + storageKey: storageKey, + keyValueStorage: keyValueStorage, + featuresTelemetryPropertyName: tasConfig.featuresTelemetryPropertyName, + assignmentContextTelemetryPropertyName: tasConfig.assignmentContextTelemetryPropertyName, + telemetryEventName: tasConfig.telemetryEventName, + endpoint: tasConfig.endpoint, + refetchInterval: refetchInterval, + }); + + await tasClient.initializePromise; + return tasClient; + } +} + +registerSingleton(ITASExperimentService, ExperimentService, false); + diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index dd6ed7685bf..41b39345f1e 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -65,6 +65,10 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.impl.setEnabled(value); } + setExperimentProperty(name: string, value: string): void { + return this.impl.setExperimentProperty(name, value); + } + get isOptedIn(): boolean { return this.impl.isOptedIn; } diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 2b32c4425df..99d36e3fa46 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -57,6 +57,10 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.impl.setEnabled(value); } + setExperimentProperty(name: string, value: string): void { + return this.impl.setExperimentProperty(name, value); + } + get isOptedIn(): boolean { return this.impl.isOptedIn; } diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index eab55d05b22..ed378fb9496 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -176,6 +176,9 @@ class TestTelemetryService implements ITelemetryService { public setEnabled(value: boolean): void { } + public setExperimentProperty(name: string, value: string): void { + } + public publicLog(eventName: string, data?: any): Promise { const event = { name: eventName, data: data }; this.events.push(event); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 1c13f30b47c..000a505240b 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -58,6 +58,7 @@ import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncAccountS import 'vs/workbench/services/sharedProcess/electron-browser/sharedProcessService'; import 'vs/workbench/services/localizations/electron-browser/localizationsService'; import 'vs/workbench/services/path/electron-browser/pathService'; +import 'vs/workbench/services/experiment/electron-browser/experimentService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; diff --git a/yarn.lock b/yarn.lock index 4e23e2cd735..3843414ac08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1108,6 +1108,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +axios@^0.19.0: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + azure-storage@^2.10.2: version "2.10.2" resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.2.tgz#3bcabdbf10e72fd0990db81116e49023c4a675b6" @@ -2371,7 +2378,7 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@3.1.0: +debug@3.1.0, debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -3566,6 +3573,13 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: inherits "^2.0.1" readable-stream "^2.0.4" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + for-in@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.5.tgz#007374e2b6d5c67420a1479bdb75a04872b738c4" @@ -9027,6 +9041,13 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" +tas-client@^0.0.950: + version "0.0.950" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.0.950.tgz#0fadc684721d5bc6d6af03b09e1ff5a83a5186fc" + integrity sha512-AvCNjvfouxJyKln+TsobOBO5KmXklL9+FlxrEPlIgaixy1TxCC2v2Vs/MflCiyHlGl+BeIStP4oAVPqo5c0pIA== + dependencies: + axios "^0.19.0" + temp@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" From e8343b0bcd2cdd6a2bbd4b4bfe7348727ceb0e39 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 23 Jul 2020 09:53:57 -0700 Subject: [PATCH 015/695] Update VSO check for auth --- src/vs/workbench/api/browser/mainThreadAuthentication.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 7e9c0ae5094..471ab42a62d 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -18,6 +18,7 @@ import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { fromNow } from 'vs/base/common/date'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { Platform, platform } from 'vs/base/common/platform'; const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser']; @@ -390,7 +391,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } const remoteConnection = this.remoteAgentService.getConnection(); - if (remoteConnection && remoteConnection.remoteAuthority && remoteConnection.remoteAuthority.startsWith('vsonline') && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) { + const isVSO = remoteConnection !== null + ? remoteConnection.remoteAuthority.startsWith('vsonline') + : platform === Platform.Web; + + if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) { addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName); return true; } From 8404410adccc98d181cc98133bc42a3724b067a5 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 23 Jul 2020 10:10:07 -0700 Subject: [PATCH 016/695] return core --- .../experiment/electron-browser/experimentService.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/workbench/services/experiment/electron-browser/experimentService.ts b/src/vs/workbench/services/experiment/electron-browser/experimentService.ts index 734c61a142b..966ea7736f8 100644 --- a/src/vs/workbench/services/experiment/electron-browser/experimentService.ts +++ b/src/vs/workbench/services/experiment/electron-browser/experimentService.ts @@ -71,6 +71,8 @@ class ExperimentServiceFilterProvider implements IExperimentationFilterProvider return this.machineId; case Filters.Language: return platform.language; + case Filters.ExtensionName: + return 'vscode-core'; // always return vscode-core for exp service case Filters.TargetPopulation: return this.targetPopulation; default: @@ -98,6 +100,7 @@ https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/A "X-VSCode–AppVersion": "appversion", "X-VSCode-Build": "build", "X-MSEdge-ClientId": "clientid", +"X-VSCode-ExtensionName": "extensionname", "X-VSCode-TargetPopulation": "targetpopulation", "X-VSCode-Language": "language" */ @@ -128,6 +131,11 @@ enum Filters { */ ClientId = 'X-MSEdge-ClientId', + /** + * Extension header. + */ + ExtensionName = 'X-VSCode-ExtensionName', + /** * The language in use by VS Code */ From 83d8407183492d4b3c27bfa12aab6b1a49dc31c5 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 23 Jul 2020 10:11:28 -0700 Subject: [PATCH 017/695] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 70e50d71d95..dd660f6421a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.48.0", - "distro": "a4dd690fc5b9555a6707734135e5e8ce0c35e5a5", + "distro": "bbedd8264622d11d10279f6e795cab138d197c40", "author": { "name": "Microsoft Corporation" }, From b26c08da6c7fba624f48b8040423c4a2d5512d0b Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 23 Jul 2020 10:52:31 -0700 Subject: [PATCH 018/695] trigger kernel update when kernel provider is registered late. --- .../workbench/contrib/notebook/browser/notebookServiceImpl.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index d4bce90bc8b..75c6bd6b1ed 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -371,6 +371,8 @@ export class NotebookService extends Disposable implements INotebookService, ICu const kernelChangeEventListener = provider.onDidChangeKernels(() => { this._onDidChangeKernels.fire(); }); + + this._onDidChangeKernels.fire(); return toDisposable(() => { kernelChangeEventListener.dispose(); d.dispose(); From 5a0df839e3e5848f2d2f8687a9866506c431a245 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 23 Jul 2020 19:48:18 +0200 Subject: [PATCH 019/695] Add featured filter --- .../common/extensionGalleryService.ts | 8 ++++++- .../extensions/browser/extensionsActions.ts | 23 ++++++++++++++++++- .../extensions/browser/extensionsViewlet.ts | 19 +++++++++++---- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 624ecc44a47..52949788147 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -162,7 +162,7 @@ class Query { withFilter(filterType: FilterType, ...values: string[]): Query { const criteria = [ ...this.state.criteria, - ...values.map(value => ({ filterType, value })) + ...values.length ? values.map(value => ({ filterType, value })) : [{ filterType }] ]; return new Query(assign({}, this.state, { criteria })); @@ -441,6 +441,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return ''; }); + // Use featured filter + text = text.replace(/\bfeatured(\s+|\b|$)/g, () => { + query = query.withFilter(FilterType.Featured); + return ''; + }); + text = text.trim(); if (text) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 9e535b75489..52da6df3f3b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1757,7 +1757,28 @@ export class ShowPopularExtensionsAction extends Action { return this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) .then(viewlet => { - viewlet.search('@sort:installs '); + viewlet.search('@popular '); + viewlet.focus(); + }); + } +} + +export class PredefinedExtensionFilterAction extends Action { + + constructor( + id: string, + label: string, + private readonly filter: string, + @IViewletService private readonly viewletService: IViewletService + ) { + super(id, label, undefined, true); + } + + run(): Promise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) + .then(viewlet => { + viewlet.search(`${this.filter} `); viewlet.focus(); }); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 4b0a9f1bd38..c7ff682f0f4 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -19,9 +19,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions'; import { - ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, - EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction, RecentlyPublishedExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction, ShowEnabledExtensionsAction + EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction, + RecentlyPublishedExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction, + ShowEnabledExtensionsAction, PredefinedExtensionFilterAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -508,9 +509,10 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE if (this.extensionGalleryService.isEnabled()) { filterActions.splice(0, 0, ...[ - this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, localize('most popular filter', "Most Popular")), + this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.featured', localize('featured filter', "Featured"), '@featured'), + this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.popular', localize('most popular filter', "Most Popular"), '@popular'), + this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.recommended', localize('most popular recommended', "Recommended"), '@recommended'), this.instantiationService.createInstance(RecentlyPublishedExtensionsAction, RecentlyPublishedExtensionsAction.ID, localize('recently published filter', "Recently Published")), - this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('recomended filter', "Recommended")), new SubmenuAction('workbench.extensions.action.filterExtensionsByCategory', localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category))), new Separator(), ]); @@ -566,7 +568,14 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } private normalizedQuery(): string { - return this.searchBox ? this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:') : ''; + return this.searchBox + ? this.searchBox.getValue() + .replace(/@category/g, 'category') + .replace(/@tag:/g, 'tag:') + .replace(/@ext:/g, 'ext:') + .replace(/@featured/g, 'featured') + .replace(/@popular/g, '@sort:installs') + : ''; } saveState(): void { From 459a9aedac2a2015eddd803b5b135f1058cbdbc8 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 23 Jul 2020 11:14:34 -0700 Subject: [PATCH 020/695] re #99399. --- src/vs/workbench/contrib/notebook/browser/media/notebook.css | 4 +++- .../contrib/notebook/browser/view/renderers/cellRenderer.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 61103a1c71d..8d38a0111cb 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -686,7 +686,9 @@ top: 0; left: 0; right: 0; - height: 100%; + width: 26px; + height: 26px; + cursor: pointer; } .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator .codicon { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index bd91c758f6f..5f97955c924 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -345,7 +345,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR editorPart.style.display = 'none'; const innerContent = DOM.append(container, $('.cell.markdown')); - const foldingIndicator = DOM.append(focusIndicator, DOM.$('.notebook-folding-indicator')); + const foldingIndicator = DOM.append(container, DOM.$('.notebook-folding-indicator')); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService)); From 2532ffff220704f717f7938c7255ce65ff72dd22 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 23 Jul 2020 20:24:02 +0200 Subject: [PATCH 021/695] Sandboxed issue reporter, fixes #101833 --- build/gulpfile.vscode.js | 2 +- src/vs/code/buildfile.js | 3 +- .../issue/issueReporter.html | 0 .../issue/issueReporter.js | 2 +- .../issue/issueReporterMain.ts | 128 +++++------------- .../issue/issueReporterModel.ts | 5 +- .../issue/issueReporterPage.ts | 0 .../issue/media/issueReporter.css | 0 .../issue/test/testReporterModel.test.ts | 2 +- .../issue/electron-main/issueMainService.ts | 34 ++++- 10 files changed, 72 insertions(+), 104 deletions(-) rename src/vs/code/{electron-browser => electron-sandbox}/issue/issueReporter.html (100%) rename src/vs/code/{electron-browser => electron-sandbox}/issue/issueReporter.js (92%) rename src/vs/code/{electron-browser => electron-sandbox}/issue/issueReporterMain.ts (87%) rename src/vs/code/{electron-browser => electron-sandbox}/issue/issueReporterModel.ts (97%) rename src/vs/code/{electron-browser => electron-sandbox}/issue/issueReporterPage.ts (100%) rename src/vs/code/{electron-browser => electron-sandbox}/issue/media/issueReporter.css (100%) rename src/vs/code/{electron-browser => electron-sandbox}/issue/test/testReporterModel.test.ts (98%) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 60777cb42cc..1bf7d36a9f6 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -77,7 +77,7 @@ const vscodeResources = [ 'out-build/vs/platform/files/**/*.md', 'out-build/vs/code/electron-browser/workbench/**', 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', - 'out-build/vs/code/electron-browser/issue/issueReporter.js', + 'out-build/vs/code/electron-sandbox/issue/issueReporter.js', 'out-build/vs/code/electron-sandbox/processExplorer/processExplorer.js', 'out-build/vs/platform/auth/common/auth.css', '!**/test/**' diff --git a/src/vs/code/buildfile.js b/src/vs/code/buildfile.js index 6547f585aed..9b1ee16680e 100644 --- a/src/vs/code/buildfile.js +++ b/src/vs/code/buildfile.js @@ -22,9 +22,8 @@ exports.collectModules = function () { createModuleDescription('vs/code/electron-main/main', []), createModuleDescription('vs/code/node/cli', []), createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), - createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []), + createModuleDescription('vs/code/electron-sandbox/issue/issueReporterMain', []), createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain', []), - createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []), createModuleDescription('vs/platform/driver/node/driver', []), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain', []) ]; diff --git a/src/vs/code/electron-browser/issue/issueReporter.html b/src/vs/code/electron-sandbox/issue/issueReporter.html similarity index 100% rename from src/vs/code/electron-browser/issue/issueReporter.html rename to src/vs/code/electron-sandbox/issue/issueReporter.html diff --git a/src/vs/code/electron-browser/issue/issueReporter.js b/src/vs/code/electron-sandbox/issue/issueReporter.js similarity index 92% rename from src/vs/code/electron-browser/issue/issueReporter.js rename to src/vs/code/electron-sandbox/issue/issueReporter.js index fdb59bf5fb8..6991c2bba2a 100644 --- a/src/vs/code/electron-browser/issue/issueReporter.js +++ b/src/vs/code/electron-sandbox/issue/issueReporter.js @@ -14,6 +14,6 @@ const bootstrapWindow = (() => { return window.MonacoBootstrapWindow; })(); -bootstrapWindow.load(['vs/code/electron-browser/issue/issueReporterMain'], function (issueReporter, configuration) { +bootstrapWindow.load(['vs/code/electron-sandbox/issue/issueReporterMain'], function (issueReporter, configuration) { issueReporter.startup(configuration); }, { forceEnableDeveloperKeybindings: true, disallowReloadKeybinding: true }); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts similarity index 87% rename from src/vs/code/electron-browser/issue/issueReporterMain.ts rename to src/vs/code/electron-sandbox/issue/issueReporterMain.ts index dce3e411d83..9e7634b2791 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -5,9 +5,8 @@ import 'vs/css!./media/issueReporter'; import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded -import * as os from 'os'; import { ElectronService, IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; -import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window'; import { $, windowOpenNoOpener, addClass } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; @@ -17,29 +16,15 @@ import { debounce } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { escape } from 'vs/base/common/strings'; -import { getDelayedChannel, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; -import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; -import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; -import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; +import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel'; +import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; -import { getLogLevel, ILogService } from 'vs/platform/log/common/log'; -import { FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc'; -import { SpdLogService } from 'vs/platform/log/node/spdlogService'; -import product from 'vs/platform/product/common/product'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { combinedAppender, LogAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; -import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; +import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; const MAX_URL_LENGTH = 2045; @@ -49,9 +34,23 @@ interface SearchResult { state?: string; } -export interface IssueReporterConfiguration extends INativeWindowConfiguration { +export interface IssueReporterConfiguration extends IWindowConfiguration { + windowId: number; + disableExtensions: boolean; data: IssueReporterData; features: IssueReporterFeatures; + os: { + type: string; + arch: string; + release: string; + }, + product: { + nameShort: string; + version: string; + commit: string | undefined; + date: string | undefined; + reportIssueUrl: string | undefined; + } } export function startup(configuration: IssueReporterConfiguration) { @@ -66,10 +65,7 @@ export function startup(configuration: IssueReporterConfiguration) { } export class IssueReporter extends Disposable { - private environmentService!: INativeEnvironmentService; private electronService!: IElectronService; - private telemetryService!: ITelemetryService; - private logService!: ILogService; private readonly issueReporterModel: IssueReporterModel; private numberOfSearchResultsDisplayed = 0; private receivedSystemInfo = false; @@ -79,7 +75,7 @@ export class IssueReporter extends Disposable { private readonly previewButton!: Button; - constructor(configuration: IssueReporterConfiguration) { + constructor(private readonly configuration: IssueReporterConfiguration) { super(); this.initServices(configuration); @@ -90,10 +86,10 @@ export class IssueReporter extends Disposable { this.issueReporterModel = new IssueReporterModel({ issueType: configuration.data.issueType || IssueType.Bug, versionInfo: { - vscodeVersion: `${product.nameShort} ${product.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`, - os: `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` + vscodeVersion: `${configuration.product.nameShort} ${configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, + os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isSnap ? ' snap' : ''}` }, - extensionsDisabled: !!this.environmentService.disableExtensions, + extensionsDisabled: !!configuration.disableExtensions, fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, selectedExtension: targetExtension, }); @@ -121,7 +117,6 @@ export class IssueReporter extends Disposable { } ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_: unknown, info: Partial) => { - this.logService.trace('issueReporter: Received performance data'); this.issueReporterModel.update(info); this.receivedPerformanceInfo = true; @@ -132,7 +127,6 @@ export class IssueReporter extends Disposable { }); ipcRenderer.on('vscode:issueSystemInfoResponse', (_: unknown, info: SystemInfo) => { - this.logService.trace('issueReporter: Received system data'); this.issueReporterModel.update({ systemInfo: info }); this.receivedSystemInfo = true; @@ -144,7 +138,6 @@ export class IssueReporter extends Disposable { if (configuration.data.issueType === IssueType.PerformanceIssue) { ipcRenderer.send('vscode:issuePerformanceInfoRequest'); } - this.logService.trace('issueReporter: Sent data requests'); if (window.document.documentElement.lang !== 'en') { show(this.getElementById('english')); @@ -266,7 +259,7 @@ export class IssueReporter extends Disposable { this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); this.updateExtensionTable(nonThemes, numberOfThemeExtesions); - if (this.environmentService.disableExtensions || installedExtensions.length === 0) { + if (this.configuration.disableExtensions || installedExtensions.length === 0) { (this.getElementById('disableExtensions')).disabled = true; } @@ -314,40 +307,13 @@ export class IssueReporter extends Disposable { } } - private initServices(configuration: INativeWindowConfiguration): void { + private initServices(configuration: IssueReporterConfiguration): void { const serviceCollection = new ServiceCollection(); const mainProcessService = new MainProcessService(configuration.windowId); serviceCollection.set(IMainProcessService, mainProcessService); this.electronService = new ElectronService(configuration.windowId, mainProcessService) as IElectronService; serviceCollection.set(IElectronService, this.electronService); - - this.environmentService = new EnvironmentService(configuration, configuration.execPath); - - const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService)); - const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); - this.logService = new FollowerLogService(loggerClient, logService); - - const sharedProcessService = createChannelSender(mainProcessService.getChannel('sharedProcess')); - - const sharedProcess = sharedProcessService.whenSharedProcessReady() - .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`)); - - const instantiationService = new InstantiationService(serviceCollection, true); - if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { - const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender'))); - const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)); - const commonProperties = resolveCommonProperties(product.commit || 'Commit unknown', product.version, configuration.machineId, product.msftInternalDomains, this.environmentService.installSourcePath); - const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot]; - const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true }; - - const telemetryService = instantiationService.createInstance(TelemetryService, config); - this._register(telemetryService); - - this.telemetryService = telemetryService; - } else { - this.telemetryService = NullTelemetryService; - } } private setEventHandlers(): void { @@ -617,11 +583,11 @@ export class IssueReporter extends Disposable { }, timeToWait * 1000); } } - }).catch(e => { - this.logSearchError(e); + }).catch(_ => { + // Ignore }); - }).catch(e => { - this.logSearchError(e); + }).catch(_ => { + // Ignore }); } @@ -648,11 +614,11 @@ export class IssueReporter extends Disposable { } else { throw new Error('Unexpected response, no candidates property'); } - }).catch((error) => { - this.logSearchError(error); + }).catch(_ => { + // Ignore }); - }).catch((error) => { - this.logSearchError(error); + }).catch(_ => { + // Ignore }); } @@ -705,18 +671,6 @@ export class IssueReporter extends Disposable { } } - private logSearchError(error: Error) { - this.logService.warn('issueReporter#search ', error.message); - type IssueReporterSearchErrorClassification = { - message: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' } - }; - - type IssueReporterSearchError = { - message: string; - }; - this.telemetryService.publicLogError2('issueReporterSearchError', { message: error.message }); - } - private setUpTypes(): void { const makeOption = (issueType: IssueType, description: string) => ``; @@ -910,15 +864,6 @@ export class IssueReporter extends Disposable { return false; } - type IssueReporterSubmitClassification = { - issueType: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - numSimilarIssuesDisplayed: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - }; - type IssueReporterSubmitEvent = { - issueType: any; - numSimilarIssuesDisplayed: number; - }; - this.telemetryService.publicLog2('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed }); this.hasBeenSubmitted = true; const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value); @@ -967,7 +912,7 @@ export class IssueReporter extends Disposable { } private getIssueUrlWithTitle(issueTitle: string): string { - let repositoryUrl = product.reportIssueUrl; + let repositoryUrl = this.configuration.product.reportIssueUrl; if (this.issueReporterModel.fileOnExtension()) { const extensionGitHubUrl = this.getExtensionGitHubUrl(); if (extensionGitHubUrl) { @@ -975,7 +920,7 @@ export class IssueReporter extends Disposable { } } - const queryStringPrefix = product.reportIssueUrl && product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&'; + const queryStringPrefix = this.configuration.product.reportIssueUrl && this.configuration.product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&'; return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`; } @@ -1136,7 +1081,7 @@ export class IssueReporter extends Disposable { private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void { const target = document.querySelector('.block-extensions .block-info'); if (target) { - if (this.environmentService.disableExtensions) { + if (this.configuration.disableExtensions) { target.innerHTML = localize('disabledExtensions', "Extensions are disabled"); return; } @@ -1193,7 +1138,6 @@ export class IssueReporter extends Disposable { // Exclude right click if (event.which < 3) { windowOpenNoOpener((event.target).href); - this.telemetryService.publicLog2('issueReporterViewSimilarIssue'); } } diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts similarity index 97% rename from src/vs/code/electron-browser/issue/issueReporterModel.ts rename to src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 47059817368..08bb22994a9 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assign } from 'vs/base/common/objects'; import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; @@ -49,7 +48,7 @@ export class IssueReporterModel { allExtensions: [] }; - this._data = initialData ? assign(defaultData, initialData) : defaultData; + this._data = initialData ? Object.assign(defaultData, initialData) : defaultData; } getData(): IssueReporterData { @@ -57,7 +56,7 @@ export class IssueReporterModel { } update(newData: Partial): void { - assign(this._data, newData); + Object.assign(this._data, newData); } serialize(): string { diff --git a/src/vs/code/electron-browser/issue/issueReporterPage.ts b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts similarity index 100% rename from src/vs/code/electron-browser/issue/issueReporterPage.ts rename to src/vs/code/electron-sandbox/issue/issueReporterPage.ts diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-sandbox/issue/media/issueReporter.css similarity index 100% rename from src/vs/code/electron-browser/issue/media/issueReporter.css rename to src/vs/code/electron-sandbox/issue/media/issueReporter.css diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts similarity index 98% rename from src/vs/code/electron-browser/issue/test/testReporterModel.test.ts rename to src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts index e7ce5b7e463..4d31af3dde7 100644 --- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; +import { IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel'; import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; import { IssueType } from 'vs/platform/issue/common/issue'; diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 3621783e893..7d0ec44e3ff 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; +import * as os from 'os'; +import product from 'vs/platform/product/common/product'; import * as objects from 'vs/base/common/objects'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { ICommonIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/common/issue'; @@ -196,12 +198,23 @@ export class IssueMainService implements ICommonIssueService { backgroundColor: data.styles.backgroundColor || DEFAULT_BACKGROUND_COLOR, webPreferences: { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, - nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, spellcheck: false, nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel) + zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), + ...this.environmentService.sandbox ? + + // Sandbox + { + sandbox: true, + contextIsolation: true + } : + + // No Sandbox + { + nodeIntegration: true + } } }); @@ -408,10 +421,23 @@ export class IssueMainService implements ICommonIssueService { machineId: this.machineId, userEnv: this.userEnv, data, - features + features, + disableExtensions: this.environmentService.disableExtensions, + os: { + type: os.type(), + arch: os.arch(), + release: os.release(), + }, + product: { + nameShort: product.nameShort, + version: product.version, + commit: product.commit, + date: product.date, + reportIssueUrl: product.reportIssueUrl + } }; - return toLauchUrl('vs/code/electron-browser/issue/issueReporter.html', windowConfiguration); + return toLauchUrl('vs/code/electron-sandbox/issue/issueReporter.html', windowConfiguration); } } From 15227279404c3245079bc7107d23e0456a36e22e Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 23 Jul 2020 12:00:45 -0700 Subject: [PATCH 022/695] Update Microsoft auth provider extension kind --- extensions/microsoft-authentication/package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 0794d50127b..9db55f18aef 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -15,6 +15,10 @@ "*", "onAuthenticationRequest:microsoft" ], + "extensionKind": [ + "ui", + "workspace" + ], "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "main": "./out/extension.js", "browser": "./dist/browser/extension.js", From df15db212370b256e36a5e9109ec11d34b12fbbd Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 23 Jul 2020 13:37:32 -0700 Subject: [PATCH 023/695] fix #99399. --- .../contrib/notebook/browser/view/renderers/codeCell.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index 1e6fbfc2eb3..73c490e54e2 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -249,6 +249,14 @@ export class CodeCell extends Disposable { } }); + this._register(templateData.editor!.onMouseDown(e => { + // prevent default on right mouse click, otherwise it will trigger unexpected focus changes + // the catch is, it means we don't allow customization of right button mouse down handlers other than the built in ones. + if (e.event.rightButton) { + e.event.preventDefault(); + } + })); + const updateFocusMode = () => viewCell.focusMode = templateData.editor!.hasWidgetFocus() ? CellFocusMode.Editor : CellFocusMode.Container; this._register(templateData.editor!.onDidFocusEditorWidget(() => { updateFocusMode(); From 0955c0e78f6d9a7ecd0022f729c73f9cc8c4030c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 23 Jul 2020 14:15:42 -0700 Subject: [PATCH 024/695] notebooks: initial implementation of 'pure' output renderers (#103125) --- .../notebook/browser/extensionPoint.ts | 10 +++- .../browser/notebookPureOutputRenderer.ts | 49 +++++++++++++++++++ .../notebook/browser/notebookServiceImpl.ts | 11 +++-- .../browser/view/renderers/webviewPreloads.ts | 15 +++++- 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts index 1f167a7a581..c7b5c94f98d 100644 --- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -27,12 +27,14 @@ namespace NotebookRendererContribution { export const viewType = 'viewType'; export const displayName = 'displayName'; export const mimeTypes = 'mimeTypes'; + export const entrypoint = 'entrypoint'; } -interface INotebookRendererContribution { +export interface INotebookRendererContribution { readonly [NotebookRendererContribution.viewType]: string; readonly [NotebookRendererContribution.displayName]: string; readonly [NotebookRendererContribution.mimeTypes]?: readonly string[]; + readonly [NotebookRendererContribution.entrypoint]?: string; } const notebookProviderContribution: IJSONSchema = { @@ -115,7 +117,11 @@ const notebookRendererContribution: IJSONSchema = { items: { type: 'string' } - } + }, + [NotebookRendererContribution.entrypoint]: { + type: 'string', + description: nls.localize('contributes.notebook.renderer.entrypoint', 'File to load in the webview to render the extension.'), + }, } } }; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts new file mode 100644 index 00000000000..c1f42f14d6c --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI, UriComponents } from 'vs/base/common/uri'; +import { INotebookRendererInfo, IOutputRenderResponse, IOutputRenderRequest } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { joinPath } from 'vs/base/common/resources'; + +/** + * A 'stub' output renderer used when the contribution has an `entrypoint` + * property. Include the entrypoint as its reload and renders an empty string. + */ +export class PureNotebookOutputRenderer implements INotebookRendererInfo { + + public readonly extensionId: ExtensionIdentifier; + public readonly extensionLocation: URI; + public readonly preloads: URI[]; + + + constructor(public readonly id: string, extension: IExtensionDescription, entrypoint: string) { + this.extensionId = extension.identifier; + this.extensionLocation = extension.extensionLocation; + this.preloads = [joinPath(extension.extensionLocation, entrypoint)]; + } + + public render(uri: URI, request: IOutputRenderRequest): Promise | undefined> { + return this.render2(uri, request); + } + + public render2(_uri: URI, request: IOutputRenderRequest): Promise | undefined> { + return Promise.resolve({ + items: request.items.map(cellInfo => ({ + key: cellInfo.key, + outputs: cellInfo.outputs.map(output => ({ + index: output.index, + outputId: output.outputId, + mimeType: output.mimeType, + handlerId: this.id, + // todo@connor4312: temp approach exploring this API: + transformedOutput: `` + })) + })) + }); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 75c6bd6b1ed..09c6094701c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -31,6 +31,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { flatten } from 'vs/base/common/arrays'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotebookKernelProviderAssociationRegistry, updateNotebookKernelProvideAssociationSchema, NotebookViewTypesExtensionRegistry } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; +import { PureNotebookOutputRenderer } from 'vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -287,8 +288,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu this.notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({ id: notebookContribution.viewType, displayName: notebookContribution.displayName, - mimeTypes: notebookContribution.mimeTypes || [] + mimeTypes: notebookContribution.mimeTypes || [], })); + + if (notebookContribution.entrypoint) { + this._notebookRenderers.set(notebookContribution.viewType, new PureNotebookOutputRenderer(notebookContribution.viewType, extension.description, notebookContribution.entrypoint)); + } } } @@ -690,7 +695,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu let orderMimeTypes: IOrderedMimeType[] = []; sorted.forEach(mimeType => { - let handlers = this.findBestMatchedRenderer(mimeType); + let handlers = this._findBestMatchedRenderer(mimeType); if (handlers.length) { const handler = handlers[0]; @@ -734,7 +739,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu }; } - findBestMatchedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { + private _findBestMatchedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); } 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 a301e99aa04..57dee431804 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -261,6 +261,8 @@ function webviewPreloads() { interface ICreateCellInfo { outputId: string; + output?: unknown; + mimeType?: string; element: HTMLElement; } @@ -374,10 +376,21 @@ function webviewPreloads() { outputNode.innerHTML = content; cellOutputContainer.appendChild(outputNode); + let pureData: { mimeType: string, output: unknown } | undefined; + const outputScript = cellOutputContainer.querySelector('script.vscode-pure-data'); + if (outputScript) { + try { pureData = JSON.parse(outputScript.innerHTML); } catch { } + } + // eval domEval(outputNode); resizeObserve(outputNode, outputId); - onDidCreateOutput.fire([data.apiNamespace, { element: outputNode, outputId }]); + onDidCreateOutput.fire([data.apiNamespace, { + element: outputNode, + output: pureData?.output, + mimeType: pureData?.mimeType, + outputId + }]); vscode.postMessage({ __vscode_notebook_message: true, From 3bd820b15c79eebf0d5a86e53d8ca189b7a98c6b Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 23 Jul 2020 14:18:59 -0700 Subject: [PATCH 025/695] Make chevron always visible and vertically centered --- src/vs/workbench/contrib/notebook/browser/media/notebook.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 8d38a0111cb..ee6e7a0db93 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -423,7 +423,6 @@ position: absolute; box-sizing: border-box; top: 0px; - opacity: 0; } .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { @@ -693,7 +692,7 @@ .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator .codicon { visibility: visible; - padding: 10px 0 0 10px; + padding: 8px 0 0 10px; } /** Theming */ From 52dfde3e21e31f7b32ccf681335d041de23f7487 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 23 Jul 2020 15:30:42 -0700 Subject: [PATCH 026/695] save kernel selection to memento if there is no isPreferred. --- .../notebook/browser/notebookEditorWidget.ts | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 1ffd922e3b0..4cf404ef0e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -69,6 +69,8 @@ export class NotebookEditorOptions extends EditorOptions { } } +const NotebookEditorActiveKernelCache = 'workbench.editor.notebook.activeKernel'; + export class NotebookEditorWidget extends Disposable implements INotebookEditor { static readonly ID: string = 'workbench.editor.notebook'; private static readonly EDITOR_MEMENTOS = new Map>(); @@ -98,6 +100,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor protected readonly _contributions: { [key: string]: INotebookEditorContribution; }; private _scrollBeyondLastLine: boolean; private readonly _memento: Memento; + private readonly _activeKernelMemento: Memento; private readonly _onDidFocusEmitter = this._register(new Emitter()); public readonly onDidFocus = this._onDidFocusEmitter.event; private _cellContextKeyManager: CellContextKeyManager | null = null; @@ -150,6 +153,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } this._activeKernel = kernel; + + const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL); + memento[this.viewModel!.viewType] = this._activeKernel?.id; + this._activeKernelMemento.saveMemento(); this._onDidChangeKernel.fire(); } @@ -196,6 +203,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor ) { super(); this._memento = new Memento(NotebookEditorWidget.ID, storageService); + this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService); this._outputRenderer = new OutputRenderer(this, this.instantiationService); this._contributions = {}; @@ -612,12 +620,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernelInfo2[], tokenSource: CancellationTokenSource) { const rawAssociations = this.configurationService.getValue(notebookKernelProviderAssociationsSettingId) || []; const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this.viewModel?.viewType)[0]?.kernelProvider; + const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL); if (userSetKernelProvider) { const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider); if (filteredKernels.length) { - this.activeKernel = filteredKernels.find(kernel => kernel.isPreferred) || filteredKernels[0]; + const cachedKernelId = memento[provider.id]; + this.activeKernel = + filteredKernels.find(kernel => kernel.isPreferred) + || filteredKernels.find(kernel => kernel.id === cachedKernelId) + || filteredKernels[0]; } else { this.activeKernel = undefined; } @@ -627,6 +640,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); } + memento[provider.id] = this._activeKernel?.id; + this._activeKernelMemento.saveMemento(); + tokenSource.dispose(); return; } @@ -634,10 +650,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // choose a preferred kernel const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId); if (kernelsFromSameExtension.length) { - const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) || kernelsFromSameExtension[0]; + const cachedKernelId = memento[provider.id]; + + const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) + || kernelsFromSameExtension.find(kernel => kernel.id === cachedKernelId) + || kernelsFromSameExtension[0]; this.activeKernel = preferedKernel; await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); await preferedKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); + + memento[provider.id] = this._activeKernel?.id; + this._activeKernelMemento.saveMemento(); tokenSource.dispose(); return; } From 13001de3b78c1f10257bf3ae80438d5b8427af2b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 21 Jul 2020 15:45:22 -0700 Subject: [PATCH 027/695] collapse with ... --- .../contrib/notebook/browser/constants.ts | 2 + .../notebook/browser/media/notebook.css | 9 ++ .../notebook/browser/notebookBrowser.ts | 10 ++- .../notebook/browser/notebookEditorWidget.ts | 6 +- .../browser/view/renderers/cellRenderer.ts | 25 +++++- .../browser/view/renderers/codeCell.ts | 60 ++++++++++--- .../browser/viewModel/baseCellViewModel.ts | 14 +-- .../browser/viewModel/codeCellViewModel.ts | 89 +++++++++++-------- 8 files changed, 156 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index ab5511a5a35..b7532eece21 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -26,3 +26,5 @@ export const EDITOR_TOP_PADDING = 12; export const EDITOR_BOTTOM_PADDING = 4; export const CELL_OUTPUT_PADDING = 14; + +export const COLLAPSED_INDICATOR_HEIGHT = 40; diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index ee6e7a0db93..752613890d7 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -211,6 +211,15 @@ outline: none !important; } +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { + font-size: 30px; +} + +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .cell-focus-indicator, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed > .monaco-toolbar { + display: none; +} + /* top and bottom borders on cells */ .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 0ddd8114cda..c791880861e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -110,6 +110,7 @@ export interface ICellViewModel { readonly model: NotebookCellTextModel; readonly id: string; readonly textBuffer: IReadonlyTextBuffer; + collapseState: CellCollapseState; dragging: boolean; handle: number; uri: URI; @@ -454,6 +455,8 @@ export interface INotebookCellList { } export interface BaseCellRenderTemplate { + editorPart: HTMLElement; + collapsedPart: HTMLElement; contextKeyService: IContextKeyService; container: HTMLElement; cellContainer: HTMLElement; @@ -471,7 +474,6 @@ export interface BaseCellRenderTemplate { } export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { - editorPart: HTMLElement; editorContainer: HTMLElement; foldingIndicator: HTMLElement; currentEditor?: ICodeEditor; @@ -535,6 +537,11 @@ export enum CellEditState { Editing } +export enum CellCollapseState { + Normal, + Collapsed +} + export enum CellFocusMode { Container, Editor @@ -553,6 +560,7 @@ export interface CellViewModelStateChangeEvent { focusModeChanged?: boolean; editStateChanged?: boolean; languageChanged?: boolean; + collapseStateChanged?: boolean; foldingStateChanged?: boolean; contentChanged?: boolean; outputIsHoveredChanged?: boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 4cf404ef0e5..1904cbc8f2c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -27,7 +27,7 @@ import { contrastBorder, editorBackground, focusBorder, foreground, registerColo import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; -import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED, INotebookDeltaDecoration } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; @@ -1705,6 +1705,7 @@ registerThemingParticipant((theme, collector) => { if (focusedCellBackgroundColor) { collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-focus-indicator, .notebookOverlay .markdown-cell-row.focused { background-color: ${focusedCellBackgroundColor} !important; }`); + collector.addRule(`.notebookOverlay .code-cell-row.focused.collapsed { background-color: ${focusedCellBackgroundColor} !important; }`); } const cellHoverBackgroundColor = theme.getColor(cellHoverBackground); @@ -1802,4 +1803,7 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${CELL_MARGIN * 2}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${CELL_BOTTOM_MARGIN}px; }`); + + collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed { height: ${COLLAPSED_INDICATOR_HEIGHT}px; }`); + collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .cell-collapsed-part { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 5f97955c924..caf7ca0a688 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -38,7 +38,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CancelCellAction, ChangeCellLanguageAction, ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, ICellViewModel, INotebookCellList, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellRenderTemplate, CellCollapseState, CellEditState, CodeCellRenderTemplate, ICellViewModel, INotebookCellList, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; @@ -299,6 +299,8 @@ abstract class AbstractCellRenderer { this.notebookEditor.selectElement(templateData.currentRenderedCell); } }, true)); + + this.setupCollapsedPart(templateData); } protected commonRenderElement(element: ICellViewModel, index: number, templateData: BaseCellRenderTemplate): void { @@ -308,6 +310,20 @@ abstract class AbstractCellRenderer { templateData.container.classList.remove(DRAGGING_CLASS); } } + + protected setupCollapsedPart(templateData: BaseCellRenderTemplate): void { + templateData.collapsedPart.textContent = '...'; + DOM.hide(templateData.collapsedPart); + templateData.disposables.add(domEvent(templateData.container, DOM.EventType.DBLCLICK)(() => { + if (!templateData.currentRenderedCell) { + return; + } + + templateData.currentRenderedCell.collapseState = templateData.currentRenderedCell.collapseState === CellCollapseState.Collapsed ? + CellCollapseState.Normal : + CellCollapseState.Collapsed; + })); + } } export class MarkdownCellRenderer extends AbstractCellRenderer implements IListRenderer { @@ -347,6 +363,8 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const innerContent = DOM.append(container, $('.cell.markdown')); const foldingIndicator = DOM.append(container, DOM.$('.notebook-folding-indicator')); + const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part')); + const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService)); @@ -354,6 +372,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); const templateData: MarkdownCellRenderTemplate = { + collapsedPart, contextKeyService, container, cellContainer: innerContent, @@ -926,6 +945,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende disposables.add(this.editorOptions.onDidChange(newValue => editor.updateOptions(newValue))); + const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part')); + const progressBar = new ProgressBar(editorPart); progressBar.hide(); disposables.add(progressBar); @@ -947,6 +968,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); const templateData: CodeCellRenderTemplate = { + editorPart, + collapsedPart, contextKeyService, container, cellContainer, diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index 73c490e54e2..b095bc54b32 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -11,7 +11,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import * as nls from 'vs/nls'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellCollapseState, CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; @@ -84,14 +84,20 @@ export class CodeCell extends Disposable { DOM.toggleClass(templateData.container, 'cell-editor-focus', viewCell.focusMode === CellFocusMode.Editor); }; + const updateForCollapseState = () => { + this.viewUpdate(); + }; this._register(viewCell.onDidChangeState((e) => { - if (!e.focusModeChanged) { - return; + if (e.focusModeChanged) { + updateForFocusMode(); } - updateForFocusMode(); + if (e.collapseStateChanged) { + updateForCollapseState(); + } })); updateForFocusMode(); + updateForCollapseState(); templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel!.metadata).editable) }); this._register(viewCell.onDidChangeState((e) => { @@ -101,22 +107,24 @@ export class CodeCell extends Disposable { })); this._register(viewCell.onDidChangeState((e) => { - if (!e.languageChanged) { - return; + if (e.languageChanged) { + const mode = this._modeService.create(viewCell.language); + templateData.editor?.getModel()?.setMode(mode.languageIdentifier); } - const mode = this._modeService.create(viewCell.language); - templateData.editor?.getModel()?.setMode(mode.languageIdentifier); + if (e.collapseStateChanged) { + // meh + this.viewCell.layoutChange({ }); + this.relayoutCell(); + } })); this._register(viewCell.onDidChangeLayout((e) => { if (e.outerWidth === undefined) { - return; - } - - const layoutInfo = templateData.editor!.getLayoutInfo(); - if (layoutInfo.width !== viewCell.layoutInfo.editorWidth) { - this.onCellWidthChange(); + const layoutInfo = templateData.editor!.getLayoutInfo(); + if (layoutInfo.width !== viewCell.layoutInfo.editorWidth) { + this.onCellWidthChange(); + } } })); @@ -299,6 +307,30 @@ export class CodeCell extends Disposable { } } + private viewUpdate(): void { + if (this.viewCell.collapseState === CellCollapseState.Collapsed) { + this.viewUpdateCollapsed(); + } else { + this.viewUpdateExpanded(); + } + } + + private viewUpdateCollapsed(): void { + DOM.hide(this.templateData.cellContainer); + DOM.show(this.templateData.collapsedPart); + this.templateData.container.classList.toggle('collapsed', true); + + this.relayoutCell(); + } + + private viewUpdateExpanded(): void { + DOM.show(this.templateData.cellContainer); + DOM.hide(this.templateData.collapsedPart); + this.templateData.container.classList.toggle('collapsed', false); + + this.relayoutCell(); + } + private layoutEditor(dimension: IDimension): void { this.templateData.editor?.layout(dimension); this.templateData.statusBarContainer.style.width = `${dimension.width}px`; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index e8ebdb2be43..c169cc9d6a2 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -14,7 +13,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { SearchParams } from 'vs/editor/common/model/textModelSearch'; import { EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, CursorAtBoundary, CellViewModelStateChangeEvent, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, CursorAtBoundary, CellViewModelStateChangeEvent, IEditableCellViewModel, INotebookCellDecorationOptions, CellCollapseState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NotebookCellMetadata, NotebookDocumentMetadata, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -61,13 +60,14 @@ export abstract class BaseCellViewModel extends Disposable { } } - private _currentTokenSource: CancellationTokenSource | undefined; - public set currentTokenSource(v: CancellationTokenSource | undefined) { - this._currentTokenSource = v; + private _collapseState: CellCollapseState = CellCollapseState.Normal; + public get collapseState(): CellCollapseState { + return this._collapseState; } - public get currentTokenSource(): CancellationTokenSource | undefined { - return this._currentTokenSource; + public set collapseState(v: CellCollapseState) { + this._collapseState = v; + this._onDidChangeState.fire({ collapseStateChanged: true }); } private _focusMode: CellFocusMode = CellFocusMode.Container; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index d5b0de57ae2..5c61672f024 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -8,8 +8,8 @@ import * as UUID from 'vs/base/common/uuid'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellViewModel, NotebookLayoutInfo, CodeCellLayoutState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellViewModel, NotebookLayoutInfo, CodeCellLayoutState, CellCollapseState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, NotebookCellOutputsSplice, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { BaseCellViewModel } from './baseCellViewModel'; @@ -101,42 +101,61 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod this._ensureOutputsTop(); const outputTotalHeight = this._outputsTop!.getTotalValue(); - let newState: CodeCellLayoutState; - let editorHeight: number; - let totalHeight: number; - if (!state.editorHeight && this._layoutInfo.layoutState === CodeCellLayoutState.FromCache) { - // No new editorHeight info - keep cached totalHeight and estimate editorHeight - editorHeight = this.estimateEditorHeight(state.font?.lineHeight); - totalHeight = this._layoutInfo.totalHeight; - newState = CodeCellLayoutState.FromCache; - } else if (state.editorHeight || this._layoutInfo.layoutState === CodeCellLayoutState.Measured) { - // Editor has been measured - editorHeight = this._editorHeight; - totalHeight = this.computeTotalHeight(this._editorHeight, outputTotalHeight); - newState = CodeCellLayoutState.Measured; + if (this.collapseState === CellCollapseState.Normal) { + let newState: CodeCellLayoutState; + let editorHeight: number; + let totalHeight: number; + if (!state.editorHeight && this._layoutInfo.layoutState === CodeCellLayoutState.FromCache) { + // No new editorHeight info - keep cached totalHeight and estimate editorHeight + editorHeight = this.estimateEditorHeight(state.font?.lineHeight); + totalHeight = this._layoutInfo.totalHeight; + newState = CodeCellLayoutState.FromCache; + } else if (state.editorHeight || this._layoutInfo.layoutState === CodeCellLayoutState.Measured) { + // Editor has been measured + editorHeight = this._editorHeight; + totalHeight = this.computeTotalHeight(this._editorHeight, outputTotalHeight); + newState = CodeCellLayoutState.Measured; + } else { + editorHeight = this.estimateEditorHeight(state.font?.lineHeight); + totalHeight = this.computeTotalHeight(editorHeight, outputTotalHeight); + newState = CodeCellLayoutState.Estimated; + } + + const indicatorHeight = editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; + const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT; + const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; + const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; + + this._layoutInfo = { + fontInfo: state.font || null, + editorHeight, + editorWidth, + outputContainerOffset, + outputTotalHeight, + totalHeight, + indicatorHeight, + bottomToolbarOffset, + layoutState: newState + }; } else { - editorHeight = this.estimateEditorHeight(state.font?.lineHeight); - totalHeight = this.computeTotalHeight(editorHeight, outputTotalHeight); - newState = CodeCellLayoutState.Estimated; + const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight; + const outputContainerOffset = COLLAPSED_INDICATOR_HEIGHT; + const totalHeight = COLLAPSED_INDICATOR_HEIGHT + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; + const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT; + + this._layoutInfo = { + fontInfo: state.font || null, + editorHeight: this._layoutInfo.editorHeight, + editorWidth: this._layoutInfo.editorWidth, + outputContainerOffset, + outputTotalHeight, + totalHeight, + indicatorHeight, + bottomToolbarOffset, + layoutState: this._layoutInfo.layoutState + }; } - const indicatorHeight = editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; - const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT; - const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; - const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; - - this._layoutInfo = { - fontInfo: state.font || null, - editorHeight, - editorWidth, - outputContainerOffset, - outputTotalHeight, - totalHeight, - indicatorHeight, - bottomToolbarOffset, - layoutState: newState - }; - if (state.editorHeight || state.outputHeight) { state.totalHeight = true; } From 4b8e0c732c0382c901489bdeb50ac530191cbd39 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 23 Jul 2020 11:59:48 -0700 Subject: [PATCH 028/695] Collapsing markdown, adding actions to menu and keybindings --- .../contrib/notebook/browser/constants.ts | 4 +- .../notebook/browser/contrib/coreActions.ts | 113 ++++++++++++++++-- .../notebook/browser/media/notebook.css | 7 +- .../notebook/browser/notebookBrowser.ts | 3 + .../notebook/browser/notebookEditorWidget.ts | 11 +- .../browser/view/renderers/cellContextKeys.ts | 16 ++- .../browser/view/renderers/cellRenderer.ts | 21 ++-- .../browser/view/renderers/codeCell.ts | 4 +- .../browser/view/renderers/markdownCell.ts | 41 +++++-- .../browser/viewModel/baseCellViewModel.ts | 10 ++ .../browser/viewModel/codeCellViewModel.ts | 10 +- .../viewModel/markdownCellViewModel.ts | 39 ++++-- 12 files changed, 217 insertions(+), 62 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index b7532eece21..b6b383674be 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -18,7 +18,7 @@ export const BOTTOM_CELL_TOOLBAR_OFFSET = 12; export const CELL_STATUSBAR_HEIGHT = 22; // Margin above editor -export const EDITOR_TOP_MARGIN = 6; +export const CELL_TOP_MARGIN = 6; export const CELL_BOTTOM_MARGIN = 6; // Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` @@ -27,4 +27,4 @@ export const EDITOR_BOTTOM_PADDING = 4; export const CELL_OUTPUT_PADDING = 14; -export const COLLAPSED_INDICATOR_HEIGHT = 40; +export const COLLAPSED_INDICATOR_HEIGHT = 24; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 499fc927e36..646709e7aa9 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; @@ -18,12 +18,12 @@ import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/context import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { BaseCellRenderTemplate, CellEditState, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_CELL_HAS_OUTPUTS, CellFocusMode, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_CELL_LIST_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookCellRunState, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { BaseCellRenderTemplate, CellCollapseState, CellEditState, CellFocusMode, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_CONTENT_COLLAPSED, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { CellKind, CellUri, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; // Notebook Commands const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; @@ -72,6 +72,11 @@ const CENTER_ACTIVE_CELL = 'notebook.centerActiveCell'; const FOCUS_IN_OUTPUT_COMMAND_ID = 'notebook.cell.focusInOutput'; const FOCUS_OUT_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutOutput'; +const COLLAPSE_CELL_CONTENT_COMMAND_ID = 'notebook.cell.collapseCellContent'; +const COLLAPSE_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.collapseCellOutput'; +const EXPAND_CELL_CONTENT_COMMAND_ID = 'notebook.cell.expandCellContent'; +const EXPAND_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.expandCellOutput'; + export const NOTEBOOK_ACTIONS_CATEGORY = { value: localize('notebookActions.category', "Notebook"), original: 'Notebook' }; export const CELL_TITLE_CELL_GROUP_ID = 'inline/cell'; @@ -1394,7 +1399,7 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_ABOVE_COMMAND_ID, - title: localize('notebookActions.joinCellAbove', "Join with Previous Cell"), + title: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KEY_J, @@ -1413,7 +1418,7 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.joinCellBelow', "Join with Next Cell"), + title: localize('notebookActions.joinCellBelow', "Join With Next Cell"), keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_J, @@ -1452,3 +1457,95 @@ registerAction2(class extends NotebookCellAction { return context.notebookEditor.revealInCenter(context.cell); } }); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: COLLAPSE_CELL_CONTENT_COMMAND_ID, + title: localize('notebookActions.collapseCellContent', "Collapse Cell Content"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_CONTENT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()), + primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_C), + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_CONTENT_COLLAPSED.toNegated()), + group: '3_collapse', + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + context.cell.collapseState = CellCollapseState.Collapsed; + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: EXPAND_CELL_CONTENT_COMMAND_ID, + title: localize('notebookActions.expandCellContent', "Expand Cell Content"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_CONTENT_COLLAPSED), + primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_C), + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_CONTENT_COLLAPSED), + group: '3_collapse', + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + context.cell.collapseState = CellCollapseState.Normal; + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: COLLAPSE_CELL_OUTPUT_COMMAND_ID, + title: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()), + primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_O), + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated()), + group: '3_collapse', + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + context.cell.collapseState = CellCollapseState.Collapsed; + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: EXPAND_CELL_OUTPUT_COMMAND_ID, + title: localize('notebookActions.expandCellOutput', "Expand Cell Output"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), + primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_C), + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), + group: '3_collapse', + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + context.cell.collapseState = CellCollapseState.Normal; + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 752613890d7..3f5bad6a679 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -212,10 +212,13 @@ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { - font-size: 30px; + opacity: 0.9; + text-decoration: underline; + cursor: pointer; + height: 100%; } -.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .cell-focus-indicator, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .notebook-folding-indicator, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed > .monaco-toolbar { display: none; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index c791880861e..4d270484bd1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -48,6 +48,8 @@ export const NOTEBOOK_CELL_RUNNABLE = new RawContextKey('notebookCellRu export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE = new RawContextKey('notebookCellMarkdownEditMode', false); // bool export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey('notebookCellRunState', undefined); // idle, running export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCellHasOutputs', false); // bool +export const NOTEBOOK_CELL_CONTENT_COLLAPSED = new RawContextKey('notebookCellContentIsCollapsed', false); // bool +export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); // bool // Kernels @@ -111,6 +113,7 @@ export interface ICellViewModel { readonly id: string; readonly textBuffer: IReadonlyTextBuffer; collapseState: CellCollapseState; + outputCollapseState: CellCollapseState; dragging: boolean; handle: number; uri: URI; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 1904cbc8f2c..fe76e1b369e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -27,7 +27,7 @@ import { contrastBorder, editorBackground, focusBorder, foreground, registerColo import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; -import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, CELL_TOP_MARGIN, EDITOR_TOP_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED, INotebookDeltaDecoration } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; @@ -1705,7 +1705,7 @@ registerThemingParticipant((theme, collector) => { if (focusedCellBackgroundColor) { collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-focus-indicator, .notebookOverlay .markdown-cell-row.focused { background-color: ${focusedCellBackgroundColor} !important; }`); - collector.addRule(`.notebookOverlay .code-cell-row.focused.collapsed { background-color: ${focusedCellBackgroundColor} !important; }`); + collector.addRule(`.notebookOverlay .code-cell-row.focused.collapsed .cell-collapsed-part { background-color: ${focusedCellBackgroundColor} !important; }`); } const cellHoverBackgroundColor = theme.getColor(cellHoverBackground); @@ -1788,7 +1788,7 @@ registerThemingParticipant((theme, collector) => { // Cell Margin collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell { margin: 0px ${CELL_MARGIN * 2}px 0px ${CELL_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { padding-top: ${EDITOR_TOP_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { padding-top: ${CELL_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row { padding-bottom: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`); @@ -1796,7 +1796,7 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell-drag-image .cell-editor-container > div { padding: ${EDITOR_TOP_PADDING}px 16px ${EDITOR_BOTTOM_PADDING}px 16px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top { height: ${EDITOR_TOP_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top { height: ${CELL_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${BOTTOM_CELL_TOOLBAR_HEIGHT}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left { width: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .cell-focus-indicator-left { width: ${CODE_CELL_LEFT_MARGIN}px; }`); @@ -1804,6 +1804,5 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${CELL_BOTTOM_MARGIN}px; }`); - collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed { height: ${COLLAPSED_INDICATOR_HEIGHT}px; }`); - collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .cell-collapsed-part { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); + collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .cell-collapsed-part { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; height: ${COLLAPSED_INDICATOR_HEIGHT}px; }`); }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts index 401d6e755f7..1e3b1685224 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts @@ -6,7 +6,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { INotebookTextModel, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; -import { NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_HAS_OUTPUTS, CellViewModelStateChangeEvent, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_HAS_OUTPUTS, CellViewModelStateChangeEvent, CellEditState, NOTEBOOK_CELL_CONTENT_COLLAPSED, CellCollapseState, NOTEBOOK_CELL_OUTPUT_COLLAPSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -19,6 +19,8 @@ export class CellContextKeyManager extends Disposable { private cellRunnable: IContextKey; private cellRunState: IContextKey; private cellHasOutputs: IContextKey; + private cellContentCollapsed: IContextKey; + private cellOutputCollapsed: IContextKey; private markdownEditMode: IContextKey; @@ -38,6 +40,8 @@ export class CellContextKeyManager extends Disposable { this.markdownEditMode = NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.bindTo(this.contextKeyService); this.cellRunState = NOTEBOOK_CELL_RUN_STATE.bindTo(this.contextKeyService); this.cellHasOutputs = NOTEBOOK_CELL_HAS_OUTPUTS.bindTo(this.contextKeyService); + this.cellContentCollapsed = NOTEBOOK_CELL_CONTENT_COLLAPSED.bindTo(this.contextKeyService); + this.cellOutputCollapsed = NOTEBOOK_CELL_OUTPUT_COLLAPSED.bindTo(this.contextKeyService); this.updateForElement(element); } @@ -59,6 +63,7 @@ export class CellContextKeyManager extends Disposable { this.updateForMetadata(); this.updateForEditState(); + this.updateForCollapseState(); this.updateForOutputs(); this.viewType.set(this.element.viewType); @@ -72,6 +77,10 @@ export class CellContextKeyManager extends Disposable { if (e.editStateChanged) { this.updateForEditState(); } + + if (e.collapseStateChanged) { + this.updateForCollapseState(); + } } private updateForMetadata() { @@ -91,6 +100,11 @@ export class CellContextKeyManager extends Disposable { } } + private updateForCollapseState() { + this.cellContentCollapsed.set(this.element.collapseState === CellCollapseState.Collapsed); + this.cellOutputCollapsed.set(this.element.outputCollapseState === CellCollapseState.Collapsed); + } + private updateForOutputs() { if (this.element instanceof CodeCellViewModel) { this.cellHasOutputs.set(this.element.outputs.length > 0); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index caf7ca0a688..619b9b234e9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -27,6 +27,7 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { IModeService } from 'vs/editor/common/services/modeService'; +import { localize } from 'vs/nls'; import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -36,7 +37,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, CELL_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CancelCellAction, ChangeCellLanguageAction, ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { BaseCellRenderTemplate, CellCollapseState, CellEditState, CodeCellRenderTemplate, ICellViewModel, INotebookCellList, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; @@ -47,7 +48,7 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind, NotebookCellMetadata, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { VerticalSeparator, createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparatorViewItem } from './cellActionView'; +import { createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView'; const $ = DOM.$; @@ -271,14 +272,14 @@ abstract class AbstractCellRenderer { if (actions.primary.length || actions.secondary.length) { templateData.container.classList.add('cell-has-toolbar-actions'); if (isCodeCellRenderTemplate(templateData)) { - templateData.focusIndicator.style.top = `${EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN}px`; - templateData.focusIndicatorRight.style.top = `${EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN}px`; + templateData.focusIndicator.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`; + templateData.focusIndicatorRight.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`; } } else { templateData.container.classList.remove('cell-has-toolbar-actions'); if (isCodeCellRenderTemplate(templateData)) { - templateData.focusIndicator.style.top = `${EDITOR_TOP_MARGIN}px`; - templateData.focusIndicatorRight.style.top = `${EDITOR_TOP_MARGIN}px`; + templateData.focusIndicator.style.top = `${CELL_TOP_MARGIN}px`; + templateData.focusIndicatorRight.style.top = `${CELL_TOP_MARGIN}px`; } } }; @@ -312,16 +313,14 @@ abstract class AbstractCellRenderer { } protected setupCollapsedPart(templateData: BaseCellRenderTemplate): void { - templateData.collapsedPart.textContent = '...'; + templateData.collapsedPart.textContent = localize('collapsedCellShowLabel', "Reveal"); DOM.hide(templateData.collapsedPart); - templateData.disposables.add(domEvent(templateData.container, DOM.EventType.DBLCLICK)(() => { + templateData.disposables.add(domEvent(templateData.collapsedPart, DOM.EventType.CLICK)(() => { if (!templateData.currentRenderedCell) { return; } - templateData.currentRenderedCell.collapseState = templateData.currentRenderedCell.collapseState === CellCollapseState.Collapsed ? - CellCollapseState.Normal : - CellCollapseState.Collapsed; + templateData.currentRenderedCell.collapseState = CellCollapseState.Normal; })); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index b095bc54b32..d3a403a30d1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -114,13 +114,13 @@ export class CodeCell extends Disposable { if (e.collapseStateChanged) { // meh - this.viewCell.layoutChange({ }); + this.viewCell.layoutChange({}); this.relayoutCell(); } })); this._register(viewCell.onDidChangeLayout((e) => { - if (e.outerWidth === undefined) { + if (e.outerWidth !== undefined) { const layoutInfo = templateData.editor!.getLayoutInfo(); if (layoutInfo.width !== viewCell.layoutInfo.editorWidth) { this.onCellWidthChange(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index f1ea22af3f5..4c8da109d43 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { hide, IDimension, show, toggleClass, addClass, removeClass } from 'vs/base/browser/dom'; +import * as DOM from 'vs/base/browser/dom'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { renderCodicons } from 'vs/base/common/codicons'; @@ -12,7 +12,7 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, INotebookEditor, MarkdownCellRenderTemplate, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, INotebookEditor, MarkdownCellRenderTemplate, ICellViewModel, CellCollapseState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellFoldingState } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -50,7 +50,7 @@ export class StatefulMarkdownCell extends Disposable { if (e.editStateChanged) { this.localDisposables.clear(); this.viewUpdate(); - } else if (e.contentChanged) { + } else if (e.contentChanged || e.collapseStateChanged) { this.viewUpdate(); } })); @@ -66,7 +66,7 @@ export class StatefulMarkdownCell extends Disposable { this.focusEditorIfNeeded(); } - toggleClass(templateData.container, 'cell-editor-focus', viewCell.focusMode === CellFocusMode.Editor); + templateData.container.classList.toggle('cell-editor-focus', viewCell.focusMode === CellFocusMode.Editor); }; this._register(viewCell.onDidChangeState((e) => { if (!e.focusModeChanged) { @@ -105,13 +105,13 @@ export class StatefulMarkdownCell extends Disposable { this._register(viewCell.onCellDecorationsChanged((e) => { e.added.forEach(options => { if (options.className) { - addClass(templateData.container, options.className); + DOM.addClass(templateData.container, options.className); } }); e.removed.forEach(options => { if (options.className) { - removeClass(templateData.container, options.className); + DOM.removeClass(templateData.container, options.className); } }); })); @@ -120,7 +120,7 @@ export class StatefulMarkdownCell extends Disposable { viewCell.getCellDecorations().forEach(options => { if (options.className) { - addClass(templateData.container, options.className); + DOM.addClass(templateData.container, options.className); } }); @@ -128,19 +128,31 @@ export class StatefulMarkdownCell extends Disposable { } private viewUpdate(): void { - if (this.viewCell.editState === CellEditState.Editing) { + if (this.viewCell.collapseState === CellCollapseState.Collapsed) { + this.viewUpdateCollapsed(); + } else if (this.viewCell.editState === CellEditState.Editing) { this.viewUpdateEditing(); } else { this.viewUpdatePreview(); } } + private viewUpdateCollapsed(): void { + DOM.show(this.templateData.collapsedPart); + DOM.hide(this.editorPart); + DOM.hide(this.markdownContainer); + this.templateData.container.classList.toggle('collapsed', true); + } + private viewUpdateEditing(): void { // switch to editing mode let editorHeight: number; - show(this.editorPart); - hide(this.markdownContainer); + DOM.show(this.editorPart); + DOM.hide(this.markdownContainer); + DOM.hide(this.templateData.collapsedPart); + this.templateData.container.classList.toggle('collapsed', false); + if (this.editor) { editorHeight = this.editor!.getContentHeight(); @@ -216,8 +228,11 @@ export class StatefulMarkdownCell extends Disposable { private viewUpdatePreview(): void { this.viewCell.detachTextEditor(); - hide(this.editorPart); - show(this.markdownContainer); + DOM.hide(this.editorPart); + DOM.hide(this.templateData.collapsedPart); + DOM.show(this.markdownContainer); + this.templateData.container.classList.toggle('collapsed', false); + this.renderedEditors.delete(this.viewCell); this.markdownContainer.innerHTML = ''; @@ -259,7 +274,7 @@ export class StatefulMarkdownCell extends Disposable { } } - private layoutEditor(dimension: IDimension): void { + private layoutEditor(dimension: DOM.IDimension): void { this.editor?.layout(dimension); this.templateData.statusBarContainer.style.width = `${dimension.width}px`; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index c169cc9d6a2..3cc03187024 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -70,6 +70,16 @@ export abstract class BaseCellViewModel extends Disposable { this._onDidChangeState.fire({ collapseStateChanged: true }); } + private _outputCollapseState: CellCollapseState = CellCollapseState.Normal; + public get outputCollapseState(): CellCollapseState { + return this._outputCollapseState; + } + + public set outputCollapseState(v: CellCollapseState) { + this._collapseState = v; + this._onDidChangeState.fire({ collapseStateChanged: true }); + } + private _focusMode: CellFocusMode = CellFocusMode.Container; get focusMode() { return this._focusMode; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 5c61672f024..8b91d298b24 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -8,7 +8,7 @@ import * as UUID from 'vs/base/common/uuid'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, CELL_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellViewModel, NotebookLayoutInfo, CodeCellLayoutState, CellCollapseState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, NotebookCellOutputsSplice, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -122,7 +122,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } const indicatorHeight = editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; - const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT; + const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT; const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; @@ -140,8 +140,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } else { const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight; const outputContainerOffset = COLLAPSED_INDICATOR_HEIGHT; - const totalHeight = COLLAPSED_INDICATOR_HEIGHT + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; - const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT; + const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_HEIGHT + outputTotalHeight; + const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; this._layoutInfo = { fontInfo: state.font || null, @@ -207,7 +207,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } private computeTotalHeight(editorHeight: number, outputsTotalHeight: number): number { - return EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT + outputsTotalHeight + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; + return EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT + outputsTotalHeight + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; } /** diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index ae2cc967583..babc68f178d 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -8,8 +8,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import * as UUID from 'vs/base/common/uuid'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_STATUSBAR_HEIGHT, EDITOR_TOP_MARGIN, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellFindMatch, ICellViewModel, MarkdownCellLayoutChangeEvent, MarkdownCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_STATUSBAR_HEIGHT, CELL_TOP_MARGIN, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellCollapseState, CellFindMatch, ICellViewModel, MarkdownCellLayoutChangeEvent, MarkdownCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; import { EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; @@ -45,7 +45,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie set editorHeight(newHeight: number) { this._editorHeight = newHeight; - this.totalHeight = this._editorHeight + EDITOR_TOP_MARGIN + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_STATUSBAR_HEIGHT; + this.totalHeight = this._editorHeight + CELL_TOP_MARGIN + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_STATUSBAR_HEIGHT; } get editorHeight() { @@ -92,16 +92,31 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie layoutChange(state: MarkdownCellLayoutChangeEvent) { // recompute - const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; - const totalHeight = state.totalHeight === undefined ? this._layoutInfo.totalHeight : state.totalHeight; - this._layoutInfo = { - fontInfo: state.font || this._layoutInfo.fontInfo, - editorWidth, - editorHeight: this._editorHeight, - bottomToolbarOffset: totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET, - totalHeight - }; + if (this.collapseState === CellCollapseState.Normal) { + const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; + const totalHeight = state.totalHeight === undefined ? this._layoutInfo.totalHeight : state.totalHeight; + + this._layoutInfo = { + fontInfo: state.font || this._layoutInfo.fontInfo, + editorWidth, + editorHeight: this._editorHeight, + bottomToolbarOffset: totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET, + totalHeight + }; + } else { + const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; + const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; + state.totalHeight = totalHeight; + + this._layoutInfo = { + fontInfo: state.font || this._layoutInfo.fontInfo, + editorWidth, + editorHeight: this._editorHeight, + bottomToolbarOffset: totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET, + totalHeight + }; + } this._onDidChangeLayout.fire(state); } From db170566b846acbba7e1c459864457b2bf536101 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 23 Jul 2020 17:20:56 -0700 Subject: [PATCH 029/695] Cell collapse indicator tweaks --- .../contrib/notebook/browser/contrib/coreActions.ts | 10 +++++----- .../contrib/notebook/browser/media/notebook.css | 9 ++++++--- .../contrib/notebook/browser/notebookEditorWidget.ts | 3 +++ .../notebook/browser/view/renderers/cellRenderer.ts | 5 ++--- .../notebook/browser/viewModel/codeCellViewModel.ts | 2 +- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 646709e7aa9..6fd71bab934 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -1537,11 +1537,11 @@ registerAction2(class extends NotebookCellAction { primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_C), weight: KeybindingWeight.WorkbenchContrib }, - menu: { - id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), - group: '3_collapse', - } + // menu: { + // id: MenuId.NotebookCellTitle, + // when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), + // group: '3_collapse', + // } }); } diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 3f5bad6a679..e9b71d92dec 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -212,10 +212,13 @@ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { - opacity: 0.9; - text-decoration: underline; cursor: pointer; - height: 100%; + box-sizing: border-box; + padding-left: 2px; +} + +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part .codicon { + margin-top: 2px; } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .notebook-folding-indicator, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index fe76e1b369e..cac17e6306c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1699,6 +1699,7 @@ registerThemingParticipant((theme, collector) => { if (cellToolbarSeperator) { collector.addRule(`.notebookOverlay .monaco-list-row > .monaco-toolbar { border: solid 1px ${cellToolbarSeperator}; }`); collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item { border: solid 1px ${cellToolbarSeperator} }`); + collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { border-bottom: solid 1px ${cellToolbarSeperator} }`); } const focusedCellBackgroundColor = theme.getColor(focusedCellBackground); @@ -1713,6 +1714,8 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .code-cell-row:not(.focused):hover .cell-focus-indicator, .notebookOverlay .code-cell-row:not(.focused).cell-output-hover .cell-focus-indicator, .notebookOverlay .markdown-cell-row:not(.focused):hover { background-color: ${cellHoverBackgroundColor} !important; }`); + collector.addRule(`.notebookOverlay .code-cell-row:not(.focused).cell-output-hover .cell-collapsed-part, + .notebookOverlay .code-cell-row:not(.focused):hover.collapsed .cell-collapsed-part { background-color: ${cellHoverBackgroundColor} !important; }`); } const focusedCellBorderColor = theme.getColor(focusedCellBorder); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 619b9b234e9..c15fab901e2 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -27,7 +27,6 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { localize } from 'vs/nls'; import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -37,7 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, CELL_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_TOP_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CancelCellAction, ChangeCellLanguageAction, ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { BaseCellRenderTemplate, CellCollapseState, CellEditState, CodeCellRenderTemplate, ICellViewModel, INotebookCellList, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; @@ -313,7 +312,7 @@ abstract class AbstractCellRenderer { } protected setupCollapsedPart(templateData: BaseCellRenderTemplate): void { - templateData.collapsedPart.textContent = localize('collapsedCellShowLabel', "Reveal"); + templateData.collapsedPart.innerHTML = renderCodicons('$(unfold)'); DOM.hide(templateData.collapsedPart); templateData.disposables.add(domEvent(templateData.collapsedPart, DOM.EventType.CLICK)(() => { if (!templateData.currentRenderedCell) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 8b91d298b24..4b5bae817f5 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -139,7 +139,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod }; } else { const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight; - const outputContainerOffset = COLLAPSED_INDICATOR_HEIGHT; + const outputContainerOffset = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT; const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_HEIGHT + outputTotalHeight; const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; From 731f9c25632dbbf01ee3a7892ad9d2791fe0260c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 23 Jul 2020 21:51:10 -0700 Subject: [PATCH 030/695] Prefer using data-href if it exists on a markdown preview link Fixes #101203 --- extensions/markdown-language-features/media/index.js | 2 +- .../markdown-language-features/preview-src/index.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/markdown-language-features/media/index.js b/extensions/markdown-language-features/media/index.js index 4075748e35b..0433f33c89a 100644 --- a/extensions/markdown-language-features/media/index.js +++ b/extensions/markdown-language-features/media/index.js @@ -1 +1 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),i="code-line";function r(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const c=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName(i)){const n=+t.getAttribute("data-line");isNaN(n)||("CODE"===t.tagName&&t.parentElement&&"PRE"===t.parentElement.tagName?e.push({element:t.parentElement,line:n}):e.push({element:t,line:n}))}}return e}})();function s(e){const t=Math.floor(e),n=c();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function a(e){const t=c(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const r=t[i],s=u(r);if(i>=1&&s.top>n){return{previous:t[o],next:r}}return i>1&&in?{previous:r,next:t[i+1]}:{previous:r}}function u({element:e}){const t=e.getBoundingClientRect(),n=e.querySelector(`.${i}`);if(n){const e=n.getBoundingClientRect(),o=Math.max(1,e.top-t.top);return{top:t.top,height:o}}return t}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=a,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=u(t),c=r.top;if(n&&n.line!==t.line){i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c)}else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=a(e);if(t){const o=u(t),i=e-window.scrollY-o.top;if(n){const e=i/(u(n).top-o.top);return r(t.line+e*(n.line-t.line))}{const e=i/o.height;return r(t.line+e)}}return null},t.getLineElementForFragment=function(e){return c().find(t=>t.element.id===e)}},function(e,t,n){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});const o=n(7),i=n(8),r=n(9),c=n(2),s=n(0),a=n(10);let u=!0;const l=new o.ActiveLineMarker,f=s.getSettings(),d=acquireVsCodeApi(),m=d.getState(),p={..."object"==typeof m?m:{},...s.getData("data-state")};d.setState(p);const g=r.createPosterForVsCode(d);window.cspAlerter.setPoster(g),window.styleLoadingMonitor.setPoster(g),window.onload=()=>{v()},i.onceDocumentLoaded(()=>{const t=p.scrollProgress;"number"!=typeof t||f.fragment?f.scrollPreviewWithEditor&&e(()=>{if(f.fragment){p.fragment=void 0,d.setState(p);const e=c.getLineElementForFragment(f.fragment);e&&(u=!0,c.scrollToRevealSourceLine(e.line))}else isNaN(f.line)||(u=!0,c.scrollToRevealSourceLine(f.line))}):e(()=>{u=!0,window.scrollTo(0,t*document.body.clientHeight)})});const h=(()=>{const e=a(e=>{u=!0,c.scrollToRevealSourceLine(e)},50);return t=>{isNaN(t)||(p.line=t,e(t))}})();let v=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,w(),v()},!0),window.addEventListener("message",e=>{if(e.data.source===f.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":h(e.data.line)}},!1),document.addEventListener("dblclick",e=>{if(!f.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=c.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||g.postMessage("didClick",{line:Math.floor(n)})});const y=["http:","https:","mailto:","vscode:","vscode-insiders:"];function w(){p.scrollProgress=window.scrollY/document.body.clientHeight,d.setState(p)}document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(y.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(g.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(w(),u)u=!1;else{const e=c.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||g.postMessage("revealLine",{line:e})}},50))}).call(this,n(4).setImmediate)},function(e,t,n){(function(e){var o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,window,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,window,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(window,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(5),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(1))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var o,i,r,c,s,a=1,u={},l=!1,f=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?o=function(e){t.nextTick((function(){p(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((r=new MessageChannel).port1.onmessage=function(e){p(e.data)},o=function(e){r.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(i=f.documentElement,o=function(e){var t=f.createElement("script");t.onreadystatechange=function(){p(e),t.onreadystatechange=null,i.removeChild(t),t=null},i.appendChild(t)}):o=function(e){setTimeout(p,0,e)}:(c="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(c)&&p(+t.data.slice(c.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),o=function(t){e.postMessage(c+t,"*")}),d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n1)for(var n=1;nnew class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}}},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,c=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,f="object"==typeof self&&self&&self.Object===Object&&self,d=l||f||Function("return this")(),m=Object.prototype.toString,p=Math.max,g=Math.min,h=function(){return d.Date.now()};function v(e,t,o){var i,r,c,s,a,u,l=0,f=!1,d=!1,m=!0;if("function"!=typeof e)throw new TypeError(n);function v(t){var n=i,o=r;return i=r=void 0,l=t,s=e.apply(o,n)}function b(e){var n=e-u;return void 0===u||n>=t||n<0||d&&e-l>=c}function T(){var e=h();if(b(e))return E(e);a=setTimeout(T,function(e){var n=t-(e-u);return d?g(n,c-(e-l)):n}(e))}function E(e){return a=void 0,m&&i?v(e):(i=r=void 0,s)}function _(){var e=h(),n=b(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(T,t),f?v(e):s}(u);if(d)return a=setTimeout(T,t),v(u)}return void 0===a&&(a=setTimeout(T,t)),s}return t=w(t)||0,y(o)&&(f=!!o.leading,c=(d="maxWait"in o)?p(w(o.maxWait)||0,t):c,m="trailing"in o?!!o.trailing:m),_.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},_.flush=function(){return void 0===a?s:E(h())},_}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function w(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&m.call(e)==i}(e))return o;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=s.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):c.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return y(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),v(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(1))}]); \ No newline at end of file +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),i="code-line";function r(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const c=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName(i)){const n=+t.getAttribute("data-line");isNaN(n)||("CODE"===t.tagName&&t.parentElement&&"PRE"===t.parentElement.tagName?e.push({element:t.parentElement,line:n}):e.push({element:t,line:n}))}}return e}})();function s(e){const t=Math.floor(e),n=c();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function a(e){const t=c(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const r=t[i],s=u(r);if(i>=1&&s.top>n){return{previous:t[o],next:r}}return i>1&&in?{previous:r,next:t[i+1]}:{previous:r}}function u({element:e}){const t=e.getBoundingClientRect(),n=e.querySelector(`.${i}`);if(n){const e=n.getBoundingClientRect(),o=Math.max(1,e.top-t.top);return{top:t.top,height:o}}return t}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=a,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=u(t),c=r.top;if(n&&n.line!==t.line){i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c)}else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=a(e);if(t){const o=u(t),i=e-window.scrollY-o.top;if(n){const e=i/(u(n).top-o.top);return r(t.line+e*(n.line-t.line))}{const e=i/o.height;return r(t.line+e)}}return null},t.getLineElementForFragment=function(e){return c().find(t=>t.element.id===e)}},function(e,t,n){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});const o=n(7),i=n(8),r=n(9),c=n(2),s=n(0),a=n(10);let u=!0;const l=new o.ActiveLineMarker,f=s.getSettings(),d=acquireVsCodeApi(),m=d.getState(),p={..."object"==typeof m?m:{},...s.getData("data-state")};d.setState(p);const g=r.createPosterForVsCode(d);window.cspAlerter.setPoster(g),window.styleLoadingMonitor.setPoster(g),window.onload=()=>{v()},i.onceDocumentLoaded(()=>{const t=p.scrollProgress;"number"!=typeof t||f.fragment?f.scrollPreviewWithEditor&&e(()=>{if(f.fragment){p.fragment=void 0,d.setState(p);const e=c.getLineElementForFragment(f.fragment);e&&(u=!0,c.scrollToRevealSourceLine(e.line))}else isNaN(f.line)||(u=!0,c.scrollToRevealSourceLine(f.line))}):e(()=>{u=!0,window.scrollTo(0,t*document.body.clientHeight)})});const h=(()=>{const e=a(e=>{u=!0,c.scrollToRevealSourceLine(e)},50);return t=>{isNaN(t)||(p.line=t,e(t))}})();let v=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,w(),v()},!0),window.addEventListener("message",e=>{if(e.data.source===f.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":h(e.data.line)}},!1),document.addEventListener("dblclick",e=>{if(!f.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=c.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||g.postMessage("didClick",{line:Math.floor(n)})});const y=["http:","https:","mailto:","vscode:","vscode-insiders:"];function w(){p.scrollProgress=window.scrollY/document.body.clientHeight,d.setState(p)}document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;let n=t.getAttribute("data-href");if(!n){if(y.some(e=>t.href.startsWith(e)))return;n=t.getAttribute("href")}return/^[a-z\-]+:/i.test(n)?void 0:(g.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(w(),u)u=!1;else{const e=c.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||g.postMessage("revealLine",{line:e})}},50))}).call(this,n(4).setImmediate)},function(e,t,n){(function(e){var o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,window,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,window,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(window,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(5),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(1))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var o,i,r,c,s,a=1,u={},l=!1,f=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?o=function(e){t.nextTick((function(){p(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((r=new MessageChannel).port1.onmessage=function(e){p(e.data)},o=function(e){r.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(i=f.documentElement,o=function(e){var t=f.createElement("script");t.onreadystatechange=function(){p(e),t.onreadystatechange=null,i.removeChild(t),t=null},i.appendChild(t)}):o=function(e){setTimeout(p,0,e)}:(c="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(c)&&p(+t.data.slice(c.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),o=function(t){e.postMessage(c+t,"*")}),d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n1)for(var n=1;nnew class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}}},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,c=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,f="object"==typeof self&&self&&self.Object===Object&&self,d=l||f||Function("return this")(),m=Object.prototype.toString,p=Math.max,g=Math.min,h=function(){return d.Date.now()};function v(e,t,o){var i,r,c,s,a,u,l=0,f=!1,d=!1,m=!0;if("function"!=typeof e)throw new TypeError(n);function v(t){var n=i,o=r;return i=r=void 0,l=t,s=e.apply(o,n)}function b(e){var n=e-u;return void 0===u||n>=t||n<0||d&&e-l>=c}function T(){var e=h();if(b(e))return E(e);a=setTimeout(T,function(e){var n=t-(e-u);return d?g(n,c-(e-l)):n}(e))}function E(e){return a=void 0,m&&i?v(e):(i=r=void 0,s)}function _(){var e=h(),n=b(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(T,t),f?v(e):s}(u);if(d)return a=setTimeout(T,t),v(u)}return void 0===a&&(a=setTimeout(T,t)),s}return t=w(t)||0,y(o)&&(f=!!o.leading,c=(d="maxWait"in o)?p(w(o.maxWait)||0,t):c,m="trailing"in o?!!o.trailing:m),_.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},_.flush=function(){return void 0===a?s:E(h())},_}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function w(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&m.call(e)==i}(e))return o;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=s.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):c.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return y(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),v(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(1))}]); \ No newline at end of file diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 4571699f762..064c30ac969 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -163,13 +163,15 @@ document.addEventListener('click', event => { return; } - // Pass through known schemes - if (passThroughLinkSchemes.some(scheme => node.href.startsWith(scheme))) { - return; + let hrefText = node.getAttribute('data-href'); + if (!hrefText) { + // Pass through known schemes + if (passThroughLinkSchemes.some(scheme => node.href.startsWith(scheme))) { + return; + } + hrefText = node.getAttribute('href'); } - const hrefText = node.getAttribute('data-href') || node.getAttribute('href'); - // If original link doesn't look like a url, delegate back to VS Code to resolve if (!/^[a-z\-]+:/i.test(hrefText)) { messaging.postMessage('openLink', { href: hrefText }); From 2fa41fb23ec030a7c904a82c4a9b4c51f6cbc6bf Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 23 Jul 2020 16:22:28 +0200 Subject: [PATCH 031/695] Add "web" as extensionKind to extensions which support this --- extensions/image-preview/package.json | 3 ++- extensions/python/package.json | 2 +- .../workbench/contrib/remote/common/remote.contribution.ts | 6 ++++-- .../services/extensions/common/extensionsRegistry.ts | 6 ++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index dd04cc77771..064ada94858 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -4,7 +4,8 @@ "description": "%description%", "extensionKind": [ "ui", - "workspace" + "workspace", + "web" ], "version": "1.0.0", "publisher": "vscode", diff --git a/extensions/python/package.json b/extensions/python/package.json index 612fcf76504..e7c75aa4ea1 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -9,7 +9,7 @@ "activationEvents": ["onLanguage:python"], "main": "./out/pythonMain", "browser": "./dist/browser/pythonMain", - "extensionKind": [ "ui", "workspace" ], + "extensionKind": [ "ui", "workspace", "web" ], "contributes": { "languages": [{ "id": "python", diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index dbd34cdc3d8..fd8ee7ee9a2 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -97,11 +97,13 @@ const extensionKindSchema: IJSONSchema = { type: 'string', enum: [ 'ui', - 'workspace' + 'workspace', + 'web' ], enumDescriptions: [ localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), - localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote."), + localize('web', "Web worker extension kind. Such an extension can execute in a web worker extension host.") ], }; diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 94dbb362146..28b7f1e14b3 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -150,11 +150,13 @@ const extensionKindSchema: IJSONSchema = { type: 'string', enum: [ 'ui', - 'workspace' + 'workspace', + 'web' ], enumDescriptions: [ nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), - nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote."), + nls.localize('web', "Web worker extension kind. Such an extension can execute in a web worker extension host.") ], }; From fd91d449f98516b7383c1709fcd2c852174c8ca0 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 23 Jul 2020 23:24:35 +0200 Subject: [PATCH 032/695] Improve default extensionKind detection --- resources/serverless/code-web.js | 3 +-- .../services/extensions/common/extensionsUtil.ts | 13 ++++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/resources/serverless/code-web.js b/resources/serverless/code-web.js index 998d88b8842..be47d30ec4b 100644 --- a/resources/serverless/code-web.js +++ b/resources/serverless/code-web.js @@ -124,7 +124,7 @@ async function getDefaultExtensionInfos() { let extensionArg = args['extension']; if (!extensionArg) { - return { extensions, locations } + return { extensions, locations }; } const extensionPaths = Array.isArray(extensionArg) ? extensionArg : [extensionArg]; @@ -164,7 +164,6 @@ async function getExtensionPackageJSON(extensionPath) { fancyLog(`${ansiColors.yellow('Warning')}: Could not find ${mainFilePath}. Use ${ansiColors.cyan('yarn gulp watch-web')} to build the built-in extensions.`); } } - packageJSON.extensionKind = ['web']; // enable for Web const packageNLSPath = path.join(extensionPath, 'package.nls.json'); const packageNLSExists = await exists(packageNLSPath); diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index 93e7069d659..575eac665f5 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -61,16 +61,23 @@ export function getExtensionKind(manifest: IExtensionManifest, productService: I // Not an UI extension if it has main if (manifest.main) { + if (manifest.browser) { + return ['workspace', 'web']; + } return ['workspace']; } - // Not an UI extension if it has dependencies or an extension pack + if (manifest.browser) { + return ['web']; + } + + // Not an UI nor web extension if it has dependencies or an extension pack if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) { return ['workspace']; } if (manifest.contributes) { - // Not an UI extension if it has no ui contributions + // Not an UI nor web extension if it has no ui contributions for (const contribution of Object.keys(manifest.contributes)) { if (!isUIExtensionPoint(contribution)) { return ['workspace']; @@ -78,7 +85,7 @@ export function getExtensionKind(manifest: IExtensionManifest, productService: I } } - return ['ui', 'workspace']; + return ['ui', 'workspace', 'web']; } let _uiExtensionPoints: Set | null = null; From 98e653e82cd0aa521c4daa161c2e9b7da1b6657b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jul 2020 10:00:48 +0200 Subject: [PATCH 033/695] Add tests for deducing extension kind --- .../extensions/common/extensionsUtil.ts | 4 ++ .../test/common/extensionsUtil.test.ts | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/vs/workbench/services/extensions/test/common/extensionsUtil.test.ts diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index 575eac665f5..65e532ee58d 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -59,6 +59,10 @@ export function getExtensionKind(manifest: IExtensionManifest, productService: I return toArray(result); } + return deduceExtensionKind(manifest); +} + +export function deduceExtensionKind(manifest: IExtensionManifest): ExtensionKind[] { // Not an UI extension if it has main if (manifest.main) { if (manifest.browser) { diff --git a/src/vs/workbench/services/extensions/test/common/extensionsUtil.test.ts b/src/vs/workbench/services/extensions/test/common/extensionsUtil.test.ts new file mode 100644 index 00000000000..b5c5671dd01 --- /dev/null +++ b/src/vs/workbench/services/extensions/test/common/extensionsUtil.test.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { deduceExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IExtensionManifest, ExtensionKind } from 'vs/platform/extensions/common/extensions'; + +suite('ExtensionKind', () => { + + function check(manifest: Partial, expected: ExtensionKind[]): void { + assert.deepEqual(deduceExtensionKind(manifest), expected); + } + + test('declarative with extension dependencies => workspace', () => { + check({ extensionDependencies: ['ext1'] }, ['workspace']); + }); + + test('declarative extension pack => workspace', () => { + check({ extensionPack: ['ext1', 'ext2'] }, ['workspace']); + }); + + test('declarative with unknown contribution point => workspace', () => { + check({ contributes: { 'unknownPoint': { something: true } } }, ['workspace']); + }); + + test('simple declarative => ui, workspace, web', () => { + check({}, ['ui', 'workspace', 'web']); + }); + + test('only browser => web', () => { + check({ browser: 'main.browser.js' }, ['web']); + }); + + test('only main => workspace', () => { + check({ main: 'main.js' }, ['workspace']); + }); + + test('main and browser => workspace, web', () => { + check({ main: 'main.js', browser: 'main.browser.js' }, ['workspace', 'web']); + }); +}); From a7b1e06283abfc7205d574a953f49a325ce9f2b3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jul 2020 10:40:39 +0200 Subject: [PATCH 034/695] Add support for web worker extensions to define their entry point via "browser" --- resources/serverless/code-web.js | 2 -- .../api/common/extHostExtensionService.ts | 14 ++++++------- .../api/node/extHostExtensionService.ts | 5 +++++ .../api/worker/extHostExtensionService.ts | 5 +++++ .../runtimeExtensionsEditor.ts | 2 +- .../format/browser/formatActionsMultiple.ts | 2 +- .../extensions/common/remoteExtensionHost.ts | 4 ++-- .../electron-browser/extensionService.ts | 4 ---- .../extensions/node/extensionPoints.ts | 21 ++++++++++++++++--- 9 files changed, 39 insertions(+), 20 deletions(-) diff --git a/resources/serverless/code-web.js b/resources/serverless/code-web.js index be47d30ec4b..7c334d442a3 100644 --- a/resources/serverless/code-web.js +++ b/resources/serverless/code-web.js @@ -154,8 +154,6 @@ async function getExtensionPackageJSON(extensionPath) { } if (packageJSON.browser) { - packageJSON.main = packageJSON.browser; - let mainFilePath = path.join(extensionPath, packageJSON.browser); if (path.extname(mainFilePath) !== '.js') { mainFilePath += '.js'; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index eaf57da1a75..c7240616746 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -193,8 +193,6 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } } - protected abstract _beforeAlmostReadyToRunExtensions(): Promise; - public async deactivateAll(): Promise { let allPromises: Promise[] = []; try { @@ -254,7 +252,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme if (!this._extensionPathIndex) { const tree = TernarySearchTree.forPaths(); const extensions = this._registry.getAllExtensionDescriptions().map(ext => { - if (!ext.main) { + if (!this._getEntryPoint(ext)) { return undefined; } return this._hostUtils.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext)); @@ -345,7 +343,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const event = getTelemetryActivationEvent(extensionDescription, reason); type ActivatePluginClassification = {} & TelemetryActivationEventFragment; this._mainThreadTelemetryProxy.$publicLog2('activatePlugin', event); - if (!extensionDescription.main) { + const entryPoint = this._getEntryPoint(extensionDescription); + if (!entryPoint) { // Treat the extension as being empty => NOT AN ERROR CASE return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE)); } @@ -355,15 +354,13 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); return Promise.all([ - this._loadCommonJSModule(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder), + this._loadCommonJSModule(joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder), this._loadExtensionContext(extensionDescription) ]).then(values => { return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); }); } - protected abstract _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; - private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); @@ -747,6 +744,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._onDidChangeRemoteConnectionData.fire(); } + protected abstract _beforeAlmostReadyToRunExtensions(): Promise; + protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined; + protected abstract _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; public abstract async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 3a02c5ce0b7..8558835c744 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -13,6 +13,7 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; class NodeModuleRequireInterceptor extends RequireInterceptor { @@ -76,6 +77,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { }; } + protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined { + return extensionDescription.main; + } + protected _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { if (module.scheme !== Schemas.file) { throw new Error(`Cannot load URI: '${module}', must be of file-scheme`); diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index 020f78b7b7f..c71ab1c7da4 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -8,6 +8,7 @@ import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHost import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; class WorkerRequireInterceptor extends RequireInterceptor { @@ -40,6 +41,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { await this._fakeModules.install(); } + protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined { + return extensionDescription.browser; + } + protected async _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { module = module.with({ path: ensureSuffix(module.path, '.js') }); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index e0542111088..1c780f7fb4f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -157,7 +157,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { this._extensionService.getExtensions().then((extensions) => { // We only deal with extensions with source code! this._extensionsDescriptions = extensions.filter((extension) => { - return !!extension.main; + return Boolean(extension.main) || Boolean(extension.browser); }); this._updateExtensions(); }); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index f856f4e05aa..26599aef2be 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -64,7 +64,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { DefaultFormatter.extensionDescriptions.push(nls.localize('nullFormatterDescription', "None")); for (const extension of extensions) { - if (extension.main) { + if (extension.main || extension.browser) { DefaultFormatter.extensionIds.push(extension.identifier.value); DefaultFormatter.extensionDescriptions.push(extension.description || ''); } diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 5a8bb9b6577..4b6c5dff01a 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -204,8 +204,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { const [telemetryInfo, remoteInitData] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]); // Collect all identifiers for extension ids which can be considered "resolved" - const resolvedExtensions = remoteInitData.allExtensions.filter(extension => !extension.main).map(extension => extension.identifier); - const hostExtensions = remoteInitData.allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier); + const resolvedExtensions = remoteInitData.allExtensions.filter(extension => !extension.main && !extension.browser).map(extension => extension.identifier); + const hostExtensions = remoteInitData.allExtensions.filter(extension => (extension.main || extension.browser) && extension.api === 'none').map(extension => extension.identifier); const workspace = this._contextService.getWorkspace(); return { commit: this._productService.commit, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 5e30ddba98e..58f61544564 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -706,10 +706,6 @@ function determineRunningLocation(productService: IProductService, configuration } if (extensionKind === 'web' && isInstalledLocally && hasLocalWebWorker) { // web worker extensions run in the local web worker if possible - if (typeof extension.browser !== 'undefined') { - // The "browser" field determines the entry point - (extension).main = extension.browser; - } return ExtensionRunningLocation.LocalWebWorker; } } diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index ab1480d249b..23795239346 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -389,9 +389,8 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { notices.push(nls.localize('extensionDescription.main1', "property `{0}` can be omitted or must be of type `string`", 'main')); return false; } else { - let normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.main); - - if (normalizedAbsolutePath.indexOf(extensionFolderPath)) { + const normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.main); + if (!normalizedAbsolutePath.startsWith(extensionFolderPath)) { notices.push(nls.localize('extensionDescription.main2', "Expected `main` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", normalizedAbsolutePath, extensionFolderPath)); // not a failure case } @@ -401,6 +400,22 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { return false; } } + if (typeof extensionDescription.browser !== 'undefined') { + if (typeof extensionDescription.browser !== 'string') { + notices.push(nls.localize('extensionDescription.browser1', "property `{0}` can be omitted or must be of type `string`", 'browser')); + return false; + } else { + const normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.browser); + if (!normalizedAbsolutePath.startsWith(extensionFolderPath)) { + notices.push(nls.localize('extensionDescription.browser2', "Expected `browser` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", normalizedAbsolutePath, extensionFolderPath)); + // not a failure case + } + } + if (typeof extensionDescription.activationEvents === 'undefined') { + notices.push(nls.localize('extensionDescription.browser3', "properties `{0}` and `{1}` must both be specified or must both be omitted", 'activationEvents', 'browser')); + return false; + } + } return true; } From 8ba686c4e4283beaacc64f8405da598dc7541e11 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 24 Jul 2020 12:06:26 +0200 Subject: [PATCH 035/695] Allow background tasks in dependsOn Part of #70283 --- .../tasks/browser/terminalTaskSystem.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index b0528ffb1b8..31c05e081c5 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -446,7 +446,7 @@ export class TerminalTaskSystem implements ITaskSystem { let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined; if (!promise) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); - promise = this.executeTask(dependencyTask, resolver, trigger, alreadyResolved); + promise = this.executeDependencyTask(dependencyTask, resolver, trigger, alreadyResolved); } promises.push(promise); if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { @@ -496,6 +496,24 @@ export class TerminalTaskSystem implements ITaskSystem { } } + private async executeDependencyTask(task: Task, resolver: ITaskResolver, trigger: string, alreadyResolved?: Map): Promise { + // If the task is a background task with a watching problem matcher, we don't wait for the whole task to finish, + // just for the problem matcher to go inactive. + if (!task.configurationProperties.isBackground) { + return this.executeTask(task, resolver, trigger, alreadyResolved); + } + + const inactivePromise = new Promise(resolve => { + const taskInactiveDisposable = this._onDidStateChange.event(taskEvent => { + if ((taskEvent.kind === TaskEventKind.Inactive) && (taskEvent.__task === task)) { + taskInactiveDisposable.dispose(); + resolve({ exitCode: 0 }); + } + }); + }); + return Promise.race([inactivePromise, this.executeTask(task, resolver, trigger, alreadyResolved)]); + } + private async resolveAndFindExecutable(systemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, cwd: string | undefined, envPath: string | undefined): Promise { const command = this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)); cwd = cwd ? this.configurationResolverService.resolve(workspaceFolder, cwd) : undefined; From bc532589c18408565ac3cc6c1eee5750fd3e970b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 24 Jul 2020 09:56:08 +0200 Subject: [PATCH 036/695] :lipstick: --- .../services/configuration/browser/configurationService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index a83f6ce3abf..8f63b3cc91d 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -99,7 +99,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]); } this._register(configurationRegistry.onDidSchemaChange(e => this.registerConfigurationSchemas())); - this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties))); + this._register(configurationRegistry.onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties))); this.workspaceEditingQueue = new Queue(); } From 8ef53a3f08b68cbe1874b7fdf1be53fd7c4102d6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 24 Jul 2020 12:12:59 +0200 Subject: [PATCH 037/695] #103238 Initialize as soon as one of the account providers is available --- .../api/browser/mainThreadAuthentication.ts | 4 +- .../userDataSync/browser/userDataSync.ts | 2 +- .../browser/authenticationService.ts | 2 + .../browser/userDataSyncWorkbenchService.ts | 67 ++++++++++--------- .../userDataSync/common/userDataSync.ts | 2 + 5 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 471ab42a62d..6c4bebff701 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import * as modes from 'vs/editor/common/modes'; import * as nls from 'vs/nls'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { IAuthenticationService, AllowedExtension, readAllowedExtensions } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { IAuthenticationService, AllowedExtension, readAllowedExtensions, getAuthenticationProviderActivationEvent } from 'vs/workbench/services/authentication/browser/authenticationService'; import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -381,7 +381,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise { - await this.extensionService.activateByEvent(`onAuthenticationRequest:${providerId}`); + await this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(providerId)); const allowList = readAllowedExtensions(this.storageService, providerId, accountName); const extensionData = allowList.find(extension => extension.id === extensionId); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 0ba2747b20b..54686714c45 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -124,7 +124,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.turningOnSyncContext = CONTEXT_TURNING_ON_STATE.bindTo(contextKeyService); this.conflictsSources = CONTEXT_CONFLICTS_SOURCES.bindTo(contextKeyService); - if (this.userDataSyncWorkbenchService.authenticationProviders.length) { + if (userDataSyncWorkbenchService.enabled) { registerConfiguration(); this.updateAccountBadge(); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 202a92003fa..9a176e4cae3 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -16,6 +16,8 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest${id}`; } + export const IAuthenticationService = createDecorator('IAuthenticationService'); export interface IAuthenticationService { diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index c5d3872e797..ff61d5d72e6 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -11,7 +11,7 @@ import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/edi import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { flatten, equals } from 'vs/base/common/arrays'; -import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { getAuthenticationProviderActivationEvent, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; @@ -64,7 +64,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat private static DONOT_USE_WORKBENCH_SESSION_STORAGE_KEY = 'userDataSyncAccount.donotUseWorkbenchSession'; private static CACHED_SESSION_STORAGE_KEY = 'userDataSyncAccountPreference'; - readonly authenticationProviders: IAuthenticationProvider[]; + private _authenticationProviders: IAuthenticationProvider[] = []; + get enabled() { return this._authenticationProviders.length > 0; } + + private availableAuthenticationProviders: IAuthenticationProvider[] = []; + get authenticationProviders() { return this.availableAuthenticationProviders; } private _accountStatus: AccountStatus = AccountStatus.Uninitialized; get accountStatus(): AccountStatus { return this._accountStatus; } @@ -95,7 +99,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @ILogService private readonly logService: ILogService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, - @IExtensionService extensionService: IExtensionService, + @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @INotificationService private readonly notificationService: INotificationService, @IProgressService private readonly progressService: IProgressService, @@ -105,35 +109,36 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, ) { super(); - this.authenticationProviders = getUserDataSyncStore(productService, configurationService)?.authenticationProviders || []; + this._authenticationProviders = getUserDataSyncStore(productService, configurationService)?.authenticationProviders || []; this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); this.accountStatusContext = CONTEXT_ACCOUNT_STATE.bindTo(contextKeyService); this.activityViewsEnablementContext = CONTEXT_ENABLE_ACTIVITY_VIEWS.bindTo(contextKeyService); this.mergesViewEnablementContext = CONTEXT_ENABLE_SYNC_MERGES_VIEW.bindTo(contextKeyService); - if (this.authenticationProviders.length) { - + if (this._authenticationProviders.length) { this.syncStatusContext.set(this.userDataSyncService.status); this._register(userDataSyncService.onDidChangeStatus(status => this.syncStatusContext.set(status))); this.syncEnablementContext.set(userDataAutoSyncService.isEnabled()); this._register(userDataAutoSyncService.onDidChangeEnablement(enabled => this.syncEnablementContext.set(enabled))); - extensionService.whenInstalledExtensionsRegistered().then(() => { - if (this.authenticationProviders.every(({ id }) => authenticationService.isAuthenticationProviderRegistered(id))) { - this.initialize(); - } else { - const disposable = this.authenticationService.onDidRegisterAuthenticationProvider(() => { - if (this.authenticationProviders.every(({ id }) => authenticationService.isAuthenticationProviderRegistered(id))) { - disposable.dispose(); - this.initialize(); - } - }); - } - }); + this.waitAndInitialize(); } } + private isSupportedAuthenticationProviderId(authenticationProviderId: string): boolean { + return this._authenticationProviders.some(({ id }) => id === authenticationProviderId); + } + + private async waitAndInitialize(): Promise { + if (this._authenticationProviders.every(({ id }) => !this.authenticationService.isAuthenticationProviderRegistered(id))) { + /* None of the providers are registered -> activate providers and wait until one of them is availabe */ + await Promise.all(this._authenticationProviders.map(({ id }) => this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id)))); + await Event.toPromise(Event.filter(this.authenticationService.onDidRegisterAuthenticationProvider, ({ id }) => this.isSupportedAuthenticationProviderId(id))); + } + await this.initialize(); + } + private async initialize(): Promise { if (this.currentSessionId === undefined && this.useWorkbenchSessionId && this.environmentService.options?.authenticationSessionId) { this.currentSessionId = this.environmentService.options.authenticationSessionId; @@ -158,8 +163,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private async update(): Promise { + + this.availableAuthenticationProviders = this._authenticationProviders.filter(({ id }) => this.authenticationService.isAuthenticationProviderRegistered(id)); + const allAccounts: Map = new Map(); - for (const { id } of this.authenticationProviders) { + for (const { id } of this.availableAuthenticationProviders) { const accounts = await this.getAccounts(id); allAccounts.set(id, accounts); } @@ -167,7 +175,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat this._all = allAccounts; const current = this.current; await this.updateToken(current); - this.updateAccountStatus(current); + this.updateAccountStatus(current ? AccountStatus.Available : AccountStatus.Unavailable); } private async getAccounts(authenticationProviderId: string): Promise { @@ -206,10 +214,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat await this.userDataSyncAccountService.updateAccount(value); } - private updateAccountStatus(current: UserDataSyncAccount | undefined): void { - // set status - const accountStatus: AccountStatus = current ? AccountStatus.Available : AccountStatus.Unavailable; - + private updateAccountStatus(accountStatus: AccountStatus): void { if (this._accountStatus !== accountStatus) { const previous = this._accountStatus; this.logService.debug('Sync account status changed', previous, accountStatus); @@ -381,10 +386,6 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } } - private isSupportedAuthenticationProviderId(authenticationProviderId: string): boolean { - return this.authenticationProviders.some(({ id }) => id === authenticationProviderId); - } - private isCurrentAccount(account: UserDataSyncAccount): boolean { return account.sessionId === this.currentSessionId; } @@ -414,15 +415,15 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private async doPick(): Promise { - if (this.authenticationProviders.length === 0) { + if (this.availableAuthenticationProviders.length === 0) { return undefined; } await this.update(); // Single auth provider and no accounts available - if (this.authenticationProviders.length === 1 && !this.all.length) { - return this.authenticationProviders[0]; + if (this.availableAuthenticationProviders.length === 1 && !this.all.length) { + return this.availableAuthenticationProviders[0]; } return new Promise(async (c, e) => { @@ -454,7 +455,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat // Signed in Accounts if (this.all.length) { - const authenticationProviders = [...this.authenticationProviders].sort(({ id }) => id === this.current?.authenticationProviderId ? -1 : 1); + const authenticationProviders = [...this.availableAuthenticationProviders].sort(({ id }) => id === this.current?.authenticationProviderId ? -1 : 1); quickPickItems.push({ type: 'separator', label: localize('signed in', "Signed in") }); for (const authenticationProvider of authenticationProviders) { const accounts = (this._all.get(authenticationProvider.id) || []).sort(({ sessionId }) => sessionId === this.current?.sessionId ? -1 : 1); @@ -472,7 +473,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } // Account proviers - for (const authenticationProvider of this.authenticationProviders) { + for (const authenticationProvider of this.availableAuthenticationProviders) { const signedInForProvider = this.all.some(account => account.authenticationProviderId === authenticationProvider.id); if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) { const providerName = this.authenticationService.getLabel(authenticationProvider.id); diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 963831be05f..73c59a33130 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -44,7 +44,9 @@ export const IUserDataSyncWorkbenchService = createDecorator Date: Fri, 24 Jul 2020 12:35:22 +0200 Subject: [PATCH 038/695] set turning on context using enablement and sync status --- .../userDataSync/browser/userDataSync.ts | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 54686714c45..1058b353fe2 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -149,9 +149,20 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo textModelResolverService.registerTextModelContentProvider(USER_DATA_SYNC_SCHEME, instantiationService.createInstance(UserDataRemoteContentProvider)); registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution); + + this._register(Event.any(userDataSyncService.onDidChangeStatus, userDataAutoSyncService.onDidChangeEnablement)(() => this.turningOnSync = !userDataAutoSyncService.isEnabled() && userDataSyncService.status !== SyncStatus.Idle)); } } + private get turningOnSync(): boolean { + return !!this.turningOnSyncContext.get(); + } + + private set turningOnSync(turningOn: boolean) { + this.turningOnSyncContext.set(turningOn); + this.updateGlobalActivityBadge(); + } + private readonly conflictsDisposables = new Map(); private onDidChangeConflicts(conflicts: [SyncResource, IResourcePreview[]][]) { if (!this.userDataAutoSyncService.isEnabled()) { @@ -417,17 +428,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private get turningOnSync(): boolean { - return !!this.turningOnSyncContext.get(); - } - - private set turningOnSync(turningOn: boolean) { - this.turningOnSyncContext.set(turningOn); - this.updateGlobalActivityBadge(); - } - private async turnOn(): Promise { - this.turningOnSync = true; try { if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) { if (!await this.askForConfirmation()) { @@ -477,8 +478,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } this.notificationService.error(localize('turn on failed', "Error while starting Sync: {0}", toErrorMessage(e))); - } finally { - this.turningOnSync = false; } } From bf9cc76621252b82ea672ebde15046532a72756a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 24 Jul 2020 13:31:07 +0200 Subject: [PATCH 039/695] show merge dialog only when there are merges from another machine --- .../browser/userDataSyncWorkbenchService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index ff61d5d72e6..525c39e291e 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -267,10 +267,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat preview = await manualSyncTask.preview(); const hasRemoteData = manualSyncTask.manifest !== null; const hasLocalData = await this.userDataSyncService.hasLocalData(); - const hasChanges = preview.some(([, { resourcePreviews }]) => resourcePreviews.some(r => r.localChange !== Change.None || r.remoteChange !== Change.None)); - const isLastSyncFromCurrentMachine = preview.every(([, { isLastSyncFromCurrentMachine }]) => isLastSyncFromCurrentMachine); + const hasMergesFromAnotherMachine = preview.some(([syncResource, { isLastSyncFromCurrentMachine, resourcePreviews }]) => + syncResource !== SyncResource.GlobalState && !isLastSyncFromCurrentMachine + && resourcePreviews.some(r => r.localChange !== Change.None || r.remoteChange !== Change.None)); - action = await this.getFirstTimeSyncAction(hasRemoteData, hasLocalData, hasChanges, isLastSyncFromCurrentMachine); + action = await this.getFirstTimeSyncAction(hasRemoteData, hasLocalData, hasMergesFromAnotherMachine); const progressDisposable = manualSyncTask.onSynchronizeResources(synchronizingResources => synchronizingResources.length ? progress.report({ message: localize('syncing resource', "Syncing {0}...", getSyncAreaLabel(synchronizingResources[0][0])) }) : undefined); try { @@ -295,12 +296,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } } - private async getFirstTimeSyncAction(hasRemoteData: boolean, hasLocalData: boolean, hasChanges: boolean, isLastSyncFromCurrentMachine: boolean): Promise { + private async getFirstTimeSyncAction(hasRemoteData: boolean, hasLocalData: boolean, hasMergesFromAnotherMachine: boolean): Promise { if (!hasLocalData /* no data on local */ || !hasRemoteData /* no data on remote */ - || !hasChanges /* no changes */ - || isLastSyncFromCurrentMachine /* has changes but last sync is from current machine */ + || !hasMergesFromAnotherMachine /* no merges with another machine */ ) { return 'merge'; } From a8b2444df154f6d1ea5e6625c18c67df9dc873a5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 24 Jul 2020 13:40:10 +0200 Subject: [PATCH 040/695] fix accepting local and remote content during merge --- .../userDataSync/common/keybindingsSync.ts | 72 +++++++++++-------- .../userDataSync/common/settingsSync.ts | 66 +++++++++-------- 2 files changed, 80 insertions(+), 58 deletions(-) diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index b0a93988b67..8062afd9963 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -177,42 +177,58 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem }]; } + protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise { + return { + ...resourcePreview, + acceptedContent, + localChange: isEqual(resource, this.localResource) ? Change.None : Change.Modified, + remoteChange: isEqual(resource, this.remoteResource) ? Change.None : Change.Modified, + }; + } + protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0]; - if (content !== null) { - if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings because the content in the file is not valid. Please open the file and correct it."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); - } - - if (localChange !== Change.None) { - this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`); - if (fileContent) { - await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null)); - } - await this.updateLocalFileContent(content, fileContent, force); - this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`); - } - - if (remoteChange !== Change.None) { - this.logService.trace(`${this.syncResourceLogLabel}: Updating remote keybindings...`); - const remoteContents = this.toSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null); - remoteUserData = await this.updateRemoteUserData(remoteContents, force ? null : remoteUserData.ref); - this.logService.info(`${this.syncResourceLogLabel}: Updated remote keybindings`); - } - - // Delete the preview - try { - await this.fileService.del(this.previewResource); - } catch (e) { /* ignore */ } - } else { + if (localChange === Change.None && remoteChange === Change.None) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`); } + if (content !== null && this.hasErrors(content)) { + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings because the content in the file is not valid. Please open the file and correct it."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); + } + + if (localChange !== Change.None) { + this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`); + if (fileContent) { + await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null)); + } + await this.updateLocalFileContent(content || '[]', fileContent, force); + this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`); + } + + if (remoteChange !== Change.None) { + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote keybindings...`); + const remoteContents = this.toSyncContent(content || '[]', remoteUserData.syncData ? remoteUserData.syncData.content : null); + remoteUserData = await this.updateRemoteUserData(remoteContents, force ? null : remoteUserData.ref); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote keybindings`); + } + + // Delete the preview + try { + await this.fileService.del(this.previewResource); + } catch (e) { /* ignore */ } + if (lastSyncUserData?.ref !== remoteUserData.ref) { this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`); - const lastSyncContent = content !== null || fileContent !== null ? this.toSyncContent(content !== null ? content : fileContent!.value.toString(), null) : null; - await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: lastSyncContent ? { version: remoteUserData.syncData!.version, machineId: remoteUserData.syncData!.machineId, content: lastSyncContent } : null }); + const lastSyncContent = content !== null ? this.toSyncContent(content, null) : null; + await this.updateLastSyncUserData({ + ref: remoteUserData.ref, + syncData: lastSyncContent ? { + version: remoteUserData.syncData ? remoteUserData.syncData.version : this.version, + machineId: remoteUserData.syncData!.machineId, + content: lastSyncContent + } : null + }); this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized keybindings`); } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 87d352779bf..778875415fc 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -193,7 +193,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement previewContent, acceptedResource: this.acceptedResource, acceptedContent, - localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None, + localChange: hasLocalChanged ? Change.Modified : Change.None, remoteChange: hasRemoteChanged ? Change.Modified : Change.None, hasConflicts, }]; @@ -206,43 +206,49 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const ignoredSettings = await this.getIgnoredSettings(); acceptedContent = updateIgnoredSettings(acceptedContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils); } - return super.updateResourcePreview(resourcePreview, resource, acceptedContent) as Promise; + return { + ...resourcePreview, + acceptedContent, + localChange: isEqual(resource, this.localResource) ? Change.None : Change.Modified, + remoteChange: isEqual(resource, this.remoteResource) ? Change.None : Change.Modified, + }; } protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0]; - if (content !== null) { - - this.validateContent(content); - - if (localChange !== Change.None) { - this.logService.trace(`${this.syncResourceLogLabel}: Updating local settings...`); - if (fileContent) { - await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(fileContent.value.toString()))); - } - await this.updateLocalFileContent(content, fileContent, force); - this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`); - } - if (remoteChange !== Change.None) { - const formatUtils = await this.getFormattingOptions(); - // Update ignored settings from remote - const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); - const ignoredSettings = await this.getIgnoredSettings(content); - content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); - this.logService.trace(`${this.syncResourceLogLabel}: Updating remote settings...`); - remoteUserData = await this.updateRemoteUserData(JSON.stringify(this.toSettingsSyncContent(content)), force ? null : remoteUserData.ref); - this.logService.info(`${this.syncResourceLogLabel}: Updated remote settings`); - } - - // Delete the preview - try { - await this.fileService.del(this.previewResource); - } catch (e) { /* ignore */ } - } else { + if (localChange === Change.None && remoteChange === Change.None) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`); } + content = content !== null ? content : '{}'; + this.validateContent(content); + + if (localChange !== Change.None) { + this.logService.trace(`${this.syncResourceLogLabel}: Updating local settings...`); + if (fileContent) { + await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(fileContent.value.toString()))); + } + await this.updateLocalFileContent(content, fileContent, force); + this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`); + } + + if (remoteChange !== Change.None) { + const formatUtils = await this.getFormattingOptions(); + // Update ignored settings from remote + const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); + const ignoredSettings = await this.getIgnoredSettings(content); + content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote settings...`); + remoteUserData = await this.updateRemoteUserData(JSON.stringify(this.toSettingsSyncContent(content)), force ? null : remoteUserData.ref); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote settings`); + } + + // Delete the preview + try { + await this.fileService.del(this.previewResource); + } catch (e) { /* ignore */ } + if (lastSyncUserData?.ref !== remoteUserData.ref) { this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized settings...`); await this.updateLastSyncUserData(remoteUserData); From 750baf0af6fc71706e356baabedf820082e8f9a3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jul 2020 13:56:43 +0200 Subject: [PATCH 041/695] Minify extension resources --- build/lib/extensions.js | 38 +++++++++++++++++--------- build/lib/extensions.ts | 60 +++++++++++++++++++++++++++-------------- build/package.json | 1 + build/yarn.lock | 5 ++++ 4 files changed, 71 insertions(+), 33 deletions(-) diff --git a/build/lib/extensions.js b/build/lib/extensions.js index d289ccf9107..0a4434bf4e3 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -22,22 +22,28 @@ const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); const buffer = require('gulp-buffer'); const json = require("gulp-json-editor"); +const jsoncParser = require("jsonc-parser"); const webpack = require('webpack'); const webpackGulp = require('webpack-stream'); const util = require('./util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; -function minimizeLanguageJSON(input) { - const tmLanguageJsonFilter = filter('**/*.tmLanguage.json', { restore: true }); +function minifyExtensionResources(input) { + const jsonFilter = filter(['**/*.json', '**/*.code-snippets'], { restore: true }); return input - .pipe(tmLanguageJsonFilter) + .pipe(jsonFilter) .pipe(buffer()) .pipe(es.mapSync((f) => { - f.contents = Buffer.from(JSON.stringify(JSON.parse(f.contents.toString('utf8')))); + const errors = []; + const value = jsoncParser.parse(f.contents.toString('utf8'), errors); + if (errors.length === 0) { + // file parsed OK => just stringify to drop whitespace and comments + f.contents = Buffer.from(JSON.stringify(value)); + } return f; })) - .pipe(tmLanguageJsonFilter.restore); + .pipe(jsonFilter.restore); } function updateExtensionPackageJSON(input, update) { const packageJsonFilter = filter('extensions/*/package.json', { restore: true }); @@ -59,6 +65,9 @@ function fromLocal(extensionPath, forWeb) { : fromLocalNormal(extensionPath); if (forWeb) { input = updateExtensionPackageJSON(input, (data) => { + delete data.scripts; + delete data.dependencies; + delete data.devDependencies; if (data.browser) { data.main = data.browser; } @@ -68,13 +77,16 @@ function fromLocal(extensionPath, forWeb) { } else if (isWebPacked) { input = updateExtensionPackageJSON(input, (data) => { + delete data.scripts; + delete data.dependencies; + delete data.devDependencies; if (data.main) { data.main = data.main.replace('/out/', /dist/); } return data; }); } - return minimizeLanguageJSON(input); + return input; } function fromLocalWebpack(extensionPath, webpackConfigFileName) { const result = es.through(); @@ -215,8 +227,8 @@ function packageLocalExtensionsStream() { .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); }); const nodeModules = gulp.src('extensions/node_modules/**', { base: '.' }); - return es.merge(nodeModules, ...localExtensions) - .pipe(util2.setExecutableBit(['**/*.sh'])); + return minifyExtensionResources(es.merge(nodeModules, ...localExtensions) + .pipe(util2.setExecutableBit(['**/*.sh']))); } exports.packageLocalExtensionsStream = packageLocalExtensionsStream; function packageLocalWebExtensionsStream() { @@ -230,10 +242,10 @@ function packageLocalWebExtensionsStream() { const extensionName = path.basename(extensionPath); return { name: extensionName, path: extensionPath }; }); - return es.merge(...localExtensionDescriptions.map(extension => { + return minifyExtensionResources(es.merge(...localExtensionDescriptions.map(extension => { return fromLocal(extension.path, true) .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); - })); + }))); } exports.packageLocalWebExtensionsStream = packageLocalWebExtensionsStream; function packageMarketplaceExtensionsStream() { @@ -241,8 +253,8 @@ function packageMarketplaceExtensionsStream() { return fromMarketplace(extension.name, extension.version, extension.metadata) .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); }); - return es.merge(extensions) - .pipe(util2.setExecutableBit(['**/*.sh'])); + return minifyExtensionResources(es.merge(extensions) + .pipe(util2.setExecutableBit(['**/*.sh']))); } exports.packageMarketplaceExtensionsStream = packageMarketplaceExtensionsStream; function packageMarketplaceWebExtensionsStream(builtInExtensions) { @@ -258,7 +270,7 @@ function packageMarketplaceWebExtensionsStream(builtInExtensions) { return data; }); }); - return es.merge(extensions); + return minifyExtensionResources(es.merge(extensions)); } exports.packageMarketplaceWebExtensionsStream = packageMarketplaceWebExtensionsStream; function scanBuiltinExtensions(extensionsRoot, forWeb) { diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 3810723d7d4..e41cc473e3c 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -21,6 +21,7 @@ import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; const buffer = require('gulp-buffer'); import json = require('gulp-json-editor'); +import * as jsoncParser from 'jsonc-parser'; const webpack = require('webpack'); const webpackGulp = require('webpack-stream'); const util = require('./util'); @@ -28,16 +29,21 @@ const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; -function minimizeLanguageJSON(input: Stream): Stream { - const tmLanguageJsonFilter = filter('**/*.tmLanguage.json', { restore: true }); +function minifyExtensionResources(input: Stream): Stream { + const jsonFilter = filter(['**/*.json', '**/*.code-snippets'], { restore: true }); return input - .pipe(tmLanguageJsonFilter) + .pipe(jsonFilter) .pipe(buffer()) .pipe(es.mapSync((f: File) => { - f.contents = Buffer.from(JSON.stringify(JSON.parse(f.contents.toString('utf8')))); + const errors: jsoncParser.ParseError[] = []; + const value = jsoncParser.parse(f.contents.toString('utf8'), errors); + if (errors.length === 0) { + // file parsed OK => just stringify to drop whitespace and comments + f.contents = Buffer.from(JSON.stringify(value)); + } return f; })) - .pipe(tmLanguageJsonFilter.restore); + .pipe(jsonFilter.restore); } function updateExtensionPackageJSON(input: Stream, update: (data: any) => any): Stream { @@ -63,6 +69,9 @@ function fromLocal(extensionPath: string, forWeb: boolean): Stream { if (forWeb) { input = updateExtensionPackageJSON(input, (data: any) => { + delete data.scripts; + delete data.dependencies; + delete data.devDependencies; if (data.browser) { data.main = data.browser; } @@ -71,6 +80,9 @@ function fromLocal(extensionPath: string, forWeb: boolean): Stream { }); } else if (isWebPacked) { input = updateExtensionPackageJSON(input, (data: any) => { + delete data.scripts; + delete data.dependencies; + delete data.devDependencies; if (data.main) { data.main = data.main.replace('/out/', /dist/); } @@ -78,7 +90,7 @@ function fromLocal(extensionPath: string, forWeb: boolean): Stream { }); } - return minimizeLanguageJSON(input); + return input; } @@ -243,7 +255,7 @@ interface IBuiltInExtension { const builtInExtensions: IBuiltInExtension[] = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')).builtInExtensions; -export function packageLocalExtensionsStream(): NodeJS.ReadWriteStream { +export function packageLocalExtensionsStream(): Stream { const localExtensionDescriptions = (glob.sync('extensions/*/package.json')) .map(manifestPath => { const extensionPath = path.dirname(path.join(root, manifestPath)); @@ -260,11 +272,13 @@ export function packageLocalExtensionsStream(): NodeJS.ReadWriteStream { }); const nodeModules = gulp.src('extensions/node_modules/**', { base: '.' }); - return es.merge(nodeModules, ...localExtensions) - .pipe(util2.setExecutableBit(['**/*.sh'])); + return minifyExtensionResources( + es.merge(nodeModules, ...localExtensions) + .pipe(util2.setExecutableBit(['**/*.sh'])) + ); } -export function packageLocalWebExtensionsStream(): NodeJS.ReadWriteStream { +export function packageLocalWebExtensionsStream(): Stream { const localExtensionDescriptions = (glob.sync('extensions/*/package.json')) .filter(manifestPath => { const packageJsonConfig = require(path.join(root, manifestPath)); @@ -276,23 +290,27 @@ export function packageLocalWebExtensionsStream(): NodeJS.ReadWriteStream { return { name: extensionName, path: extensionPath }; }); - return es.merge(...localExtensionDescriptions.map(extension => { - return fromLocal(extension.path, true) - .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); - })); + return minifyExtensionResources( + es.merge(...localExtensionDescriptions.map(extension => { + return fromLocal(extension.path, true) + .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); + })) + ); } -export function packageMarketplaceExtensionsStream(): NodeJS.ReadWriteStream { +export function packageMarketplaceExtensionsStream(): Stream { const extensions = builtInExtensions.map(extension => { return fromMarketplace(extension.name, extension.version, extension.metadata) .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); }); - return es.merge(extensions) - .pipe(util2.setExecutableBit(['**/*.sh'])); + return minifyExtensionResources( + es.merge(extensions) + .pipe(util2.setExecutableBit(['**/*.sh'])) + ); } -export function packageMarketplaceWebExtensionsStream(builtInExtensions: IBuiltInExtension[]): NodeJS.ReadWriteStream { +export function packageMarketplaceWebExtensionsStream(builtInExtensions: IBuiltInExtension[]): Stream { const extensions = builtInExtensions .map(extension => { const input = fromMarketplace(extension.name, extension.version, extension.metadata) @@ -305,7 +323,9 @@ export function packageMarketplaceWebExtensionsStream(builtInExtensions: IBuiltI return data; }); }); - return es.merge(extensions); + return minifyExtensionResources( + es.merge(extensions) + ); } export interface IScannedBuiltinExtension { @@ -336,7 +356,7 @@ export function scanBuiltinExtensions(extensionsRoot: string, forWeb: boolean): if (packageNLS) { // temporary - packageJSON = translatePackageJSON(packageJSON, path.join(extensionsRoot, extensionFolder, packageNLS)) + packageJSON = translatePackageJSON(packageJSON, path.join(extensionsRoot, extensionFolder, packageNLS)); } scannedExtensions.push({ extensionPath: extensionFolder, diff --git a/build/package.json b/build/package.json index 22206229ffb..5cf6fe5202b 100644 --- a/build/package.json +++ b/build/package.json @@ -39,6 +39,7 @@ "gulp-sourcemaps": "^1.11.0", "gulp-uglify": "^3.0.0", "iconv-lite-umd": "0.6.8", + "jsonc-parser": "^2.3.0", "mime": "^1.3.4", "minimatch": "3.0.4", "minimist": "^1.2.3", diff --git a/build/yarn.lock b/build/yarn.lock index 79909133c5b..94529f13ba0 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1600,6 +1600,11 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +jsonc-parser@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.0.tgz#7c7fc988ee1486d35734faaaa866fadb00fa91ee" + integrity sha512-b0EBt8SWFNnixVdvoR2ZtEGa9ZqLhbJnOjezn+WP+8kspFm+PFYDN8Z4Bc7pRlDjvuVcADSUkroIuTWWn/YiIA== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" From 91cdca1bdb0ea233e0c2d5bcb5f4ca60efc0f30e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jul 2020 13:57:46 +0200 Subject: [PATCH 042/695] Do not ship unnecessary files with extensions --- extensions/emmet/.vscodeignore | 3 +- extensions/extension-editing/.vscodeignore | 3 +- .../github-authentication/.vscodeignore | 2 + .../html-language-features/.vscodeignore | 2 + extensions/image-preview/.vscodeignore | 1 + .../markdown-language-features/.vscodeignore | 1 + extensions/merge-conflict/.vscodeignore | 3 +- .../microsoft-authentication/.vscodeignore | 6 +- extensions/npm/.vscodeignore | 3 +- extensions/package.json | 3 - extensions/python/.vscodeignore | 4 +- extensions/search-result/.vscodeignore | 6 ++ extensions/typescript-basics/.vscodeignore | 1 + .../.vscodeignore | 2 + extensions/yarn.lock | 79 ------------------- 15 files changed, 31 insertions(+), 88 deletions(-) create mode 100644 extensions/search-result/.vscodeignore diff --git a/extensions/emmet/.vscodeignore b/extensions/emmet/.vscodeignore index 573d91ebe6b..8180a27356e 100644 --- a/extensions/emmet/.vscodeignore +++ b/extensions/emmet/.vscodeignore @@ -3,7 +3,8 @@ src/** out/** tsconfig.json extension.webpack.config.js +extension-browser.webpack.config.js CONTRIBUTING.md cgmanifest.json yarn.lock -.vscode \ No newline at end of file +.vscode diff --git a/extensions/extension-editing/.vscodeignore b/extensions/extension-editing/.vscodeignore index 9d384dd9061..de8e6dc5913 100644 --- a/extensions/extension-editing/.vscodeignore +++ b/extensions/extension-editing/.vscodeignore @@ -3,4 +3,5 @@ src/** tsconfig.json out/** extension.webpack.config.js -yarn.lock \ No newline at end of file +extension-browser.webpack.config.js +yarn.lock diff --git a/extensions/github-authentication/.vscodeignore b/extensions/github-authentication/.vscodeignore index ee85b884502..5f3350adfb6 100644 --- a/extensions/github-authentication/.vscodeignore +++ b/extensions/github-authentication/.vscodeignore @@ -1,8 +1,10 @@ +.gitignore src/** !src/common/config.json out/** build/** extension.webpack.config.js +extension-browser.webpack.config.js tsconfig.json yarn.lock README.md diff --git a/extensions/html-language-features/.vscodeignore b/extensions/html-language-features/.vscodeignore index 4215adf6eca..a4a4702c351 100644 --- a/extensions/html-language-features/.vscodeignore +++ b/extensions/html-language-features/.vscodeignore @@ -16,5 +16,7 @@ server/.npmignore yarn.lock server/extension.webpack.config.js extension.webpack.config.js +server/extension-browser.webpack.config.js +extension-browser.webpack.config.js CONTRIBUTING.md cgmanifest.json diff --git a/extensions/image-preview/.vscodeignore b/extensions/image-preview/.vscodeignore index 30d948fbc66..bcb886a094d 100644 --- a/extensions/image-preview/.vscodeignore +++ b/extensions/image-preview/.vscodeignore @@ -4,6 +4,7 @@ tsconfig.json out/test/** out/** extension.webpack.config.js +extension-browser.webpack.config.js cgmanifest.json yarn.lock preview-src/** diff --git a/extensions/markdown-language-features/.vscodeignore b/extensions/markdown-language-features/.vscodeignore index bcb886a094d..9f1e0620775 100644 --- a/extensions/markdown-language-features/.vscodeignore +++ b/extensions/markdown-language-features/.vscodeignore @@ -1,4 +1,5 @@ test/** +test-workspace/** src/** tsconfig.json out/test/** diff --git a/extensions/merge-conflict/.vscodeignore b/extensions/merge-conflict/.vscodeignore index 36e8b0714fa..f071cfb7c71 100644 --- a/extensions/merge-conflict/.vscodeignore +++ b/extensions/merge-conflict/.vscodeignore @@ -2,4 +2,5 @@ src/** tsconfig.json out/** extension.webpack.config.js -yarn.lock \ No newline at end of file +extension-browser.webpack.config.js +yarn.lock diff --git a/extensions/microsoft-authentication/.vscodeignore b/extensions/microsoft-authentication/.vscodeignore index ed3f9d37c1f..46f23a20dba 100644 --- a/extensions/microsoft-authentication/.vscodeignore +++ b/extensions/microsoft-authentication/.vscodeignore @@ -1,10 +1,14 @@ .vscode/** .vscode-test/** out/test/** +out/** +extension.webpack.config.js +extension-browser.webpack.config.js +yarn.lock src/** .gitignore vsc-extension-quickstart.md **/tsconfig.json **/tslint.json **/*.map -**/*.ts \ No newline at end of file +**/*.ts diff --git a/extensions/npm/.vscodeignore b/extensions/npm/.vscodeignore index 27bb76ffd24..7700b94ebb0 100644 --- a/extensions/npm/.vscodeignore +++ b/extensions/npm/.vscodeignore @@ -3,4 +3,5 @@ out/** tsconfig.json .vscode/** extension.webpack.config.js -yarn.lock \ No newline at end of file +extension-browser.webpack.config.js +yarn.lock diff --git a/extensions/package.json b/extensions/package.json index 69f5ea275b4..665553eeeae 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -7,8 +7,5 @@ }, "scripts": { "postinstall": "node ./postinstall" - }, - "devDependencies": { - "rimraf": "^3.0.2" } } diff --git a/extensions/python/.vscodeignore b/extensions/python/.vscodeignore index 4d5a14fc91e..b5c95d0fb64 100644 --- a/extensions/python/.vscodeignore +++ b/extensions/python/.vscodeignore @@ -1,6 +1,8 @@ test/** src/** +out/** tsconfig.json extension.webpack.config.js +extension-browser.webpack.config.js cgmanifest.json -.vscode \ No newline at end of file +.vscode diff --git a/extensions/search-result/.vscodeignore b/extensions/search-result/.vscodeignore new file mode 100644 index 00000000000..da3d2763686 --- /dev/null +++ b/extensions/search-result/.vscodeignore @@ -0,0 +1,6 @@ +src/** +out/** +tsconfig.json +extension.webpack.config.js +extension-browser.webpack.config.js +yarn.lock diff --git a/extensions/typescript-basics/.vscodeignore b/extensions/typescript-basics/.vscodeignore index 06c7b11c5e6..0a0a50bc3e0 100644 --- a/extensions/typescript-basics/.vscodeignore +++ b/extensions/typescript-basics/.vscodeignore @@ -3,3 +3,4 @@ src/** test/** tsconfig.json cgmanifest.json +syntaxes/Readme.md diff --git a/extensions/typescript-language-features/.vscodeignore b/extensions/typescript-language-features/.vscodeignore index 1edbc2a7b5e..079f06f08d9 100644 --- a/extensions/typescript-language-features/.vscodeignore +++ b/extensions/typescript-language-features/.vscodeignore @@ -1,8 +1,10 @@ build/** src/** test/** +test-workspace/** out/** tsconfig.json extension.webpack.config.js +extension-browser.webpack.config.js cgmanifest.json yarn.lock diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 86223e77212..102d128edb6 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,86 +2,7 @@ # yarn lockfile v1 -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - typescript@3.9.7: version "3.9.7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= From d42e9162b92e2178c2f55b798657c9a75fbca89b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jul 2020 14:38:10 +0200 Subject: [PATCH 043/695] Remove some extension package.json patching --- build/lib/extensions.js | 14 +------------- build/lib/extensions.ts | 13 +------------ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 0a4434bf4e3..3726563cede 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -63,19 +63,7 @@ function fromLocal(extensionPath, forWeb) { let input = isWebPacked ? fromLocalWebpack(extensionPath, webpackConfigFileName) : fromLocalNormal(extensionPath); - if (forWeb) { - input = updateExtensionPackageJSON(input, (data) => { - delete data.scripts; - delete data.dependencies; - delete data.devDependencies; - if (data.browser) { - data.main = data.browser; - } - data.extensionKind = ['web']; - return data; - }); - } - else if (isWebPacked) { + if (isWebPacked) { input = updateExtensionPackageJSON(input, (data) => { delete data.scripts; delete data.dependencies; diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index e41cc473e3c..2efa0947b7b 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -67,18 +67,7 @@ function fromLocal(extensionPath: string, forWeb: boolean): Stream { ? fromLocalWebpack(extensionPath, webpackConfigFileName) : fromLocalNormal(extensionPath); - if (forWeb) { - input = updateExtensionPackageJSON(input, (data: any) => { - delete data.scripts; - delete data.dependencies; - delete data.devDependencies; - if (data.browser) { - data.main = data.browser; - } - data.extensionKind = ['web']; - return data; - }); - } else if (isWebPacked) { + if (isWebPacked) { input = updateExtensionPackageJSON(input, (data: any) => { delete data.scripts; delete data.dependencies; From e8c3ff351bdf2d93e12fff29a6daed3124190f12 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 24 Jul 2020 06:46:02 -0700 Subject: [PATCH 044/695] Log ext host terminal process requests --- src/vs/workbench/api/browser/mainThreadTerminalService.ts | 3 +++ src/vs/workbench/api/node/extHostTerminalService.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index c53cedd6be0..41cd7d3082a 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -15,6 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; +import { ILogService } from 'vs/platform/log/common/log'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -40,6 +41,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService, + @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._remoteAuthority = extHostContext.remoteAuthority; @@ -259,6 +261,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape env: request.shellLaunchConfig.env }; + this._logService.trace('Spawning ext host process', { terminalId: proxy.terminalId, shellLaunchConfigDto, request }); this._proxy.$spawnExtHostProcess( proxy.terminalId, shellLaunchConfigDto, diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 462038c5d36..c777688550a 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -200,7 +200,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { this._proxy.$sendResolvedLaunchConfig(id, shellLaunchConfig); // Fork the process and listen for messages - this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env); + this._logService.debug(`Terminal process launching on ext host`, { shellLaunchConfig, initialCwd, cols, rows, env }); // TODO: Support conpty on remote, it doesn't seem to work for some reason? // TODO: When conpty is enabled, only enable it when accessibilityMode is off const enableConpty = false; //terminalConfig.get('windowsEnableConpty') as boolean; From 2de499749410cf099f4f9a224b82e75e507f1659 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jul 2020 15:53:25 +0200 Subject: [PATCH 045/695] Allow ms-vscode.references-view to use proposed API when running from sources --- src/vs/platform/product/common/product.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 67083e24df7..4e89d307994 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -23,7 +23,10 @@ if (isWeb) { version: '1.48.0-dev', nameLong: 'Visual Studio Code Web Dev', nameShort: 'VSCode Web Dev', - urlProtocol: 'code-oss' + urlProtocol: 'code-oss', + extensionAllowedProposedApi: [ + 'ms-vscode.references-view' + ], }); } } From 3c96ef7d897bac3b31b02315f58ec448aea45f61 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Fri, 24 Jul 2020 09:11:23 -0700 Subject: [PATCH 046/695] Update Microsoft auth callbacks --- extensions/microsoft-authentication/src/AADHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 24599f62b22..670ebc6a0ce 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -339,7 +339,7 @@ export class AzureActiveDirectoryService { } private getCallbackEnvironment(callbackUri: vscode.Uri): string { - if (callbackUri.authority.endsWith('.workspaces.github.com')) { + if (callbackUri.authority.endsWith('.workspaces.github.com') || callbackUri.authority.endsWith('.github.dev')) { return `${callbackUri.authority},`; } From b2d1932f685cff561106046a5da9cfa9bf199468 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 24 Jul 2020 09:53:09 -0700 Subject: [PATCH 047/695] fixes #103166 --- .../platform/contextview/browser/contextMenuHandler.css | 8 -------- src/vs/platform/contextview/browser/contextMenuHandler.ts | 6 ++++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.css b/src/vs/platform/contextview/browser/contextMenuHandler.css index ef8a5236187..51a9e400923 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.css +++ b/src/vs/platform/contextview/browser/contextMenuHandler.css @@ -7,11 +7,3 @@ min-width: 130px; } -.context-view-block { - position: fixed; - cursor: initial; - left:0; - top:0; - width: 100%; - height: 100%; -} diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index 5f037f5c219..21162d2b5bf 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -66,6 +66,12 @@ export class ContextMenuHandler { // Render invisible div to block mouse interaction in the rest of the UI if (this.options.blockMouse) { this.block = container.appendChild($('.context-view-block')); + this.block.style.position = 'fixed'; + this.block.style.cursor = 'initial'; + this.block.style.left = '0'; + this.block.style.top = '0'; + this.block.style.width = '100%'; + this.block.style.height = '100%'; domEvent(this.block, EventType.MOUSE_DOWN)((e: MouseEvent) => e.stopPropagation()); } From 33659eda9f03e6592798f5faad4e58af14a1d0c1 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Fri, 24 Jul 2020 12:38:43 -0700 Subject: [PATCH 048/695] Add codeExchangeProxyEndpoints to web api --- extensions/microsoft-authentication/src/AADHelper.ts | 5 ++++- .../authentication/browser/authenticationService.ts | 6 ++++++ src/vs/workbench/workbench.web.api.ts | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 670ebc6a0ce..f548b2ba528 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -471,7 +471,10 @@ export class AzureActiveDirectoryService { redirect_uri: redirectUrl }); - const result = await fetch(`${loginEndpointUrl}${tenant}/oauth2/v2.0/token`, { + const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints'); + const endpoint = proxyEndpoints && proxyEndpoints['microsoft'] || `${loginEndpointUrl}${tenant}/oauth2/v2.0/token`; + + const result = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 9a176e4cae3..c7d73ff33f5 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -15,6 +15,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest${id}`; } @@ -70,6 +71,11 @@ export interface SessionRequestInfo { [scopes: string]: SessionRequest; } +CommandsRegistry.registerCommand('workbench.getCodeExchangeProxyEndpoints', function (accessor, _) { + const environmentService = accessor.get(IWorkbenchEnvironmentService); + return environmentService.options?.codeExchangeProxyEndpoints; +}); + export class AuthenticationService extends Disposable implements IAuthenticationService { declare readonly _serviceBrand: undefined; private _placeholderMenuItem: IDisposable | undefined; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 40215a64939..7932149e035 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -345,6 +345,11 @@ interface IWorkbenchConstructionOptions { */ readonly driver?: boolean; + /** + * Endpoints to be used for proxying authentication code exchange calls in the browser. + */ + readonly codeExchangeProxyEndpoints?: { [providerId: string]: string } + //#endregion } From d43491b79d3707b5fae068af934a0f324d33e61b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 24 Jul 2020 13:49:59 -0700 Subject: [PATCH 049/695] Reduce number of files copied for TS web build --- .../extension-browser.webpack.config.js | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/extensions/typescript-language-features/extension-browser.webpack.config.js b/extensions/typescript-language-features/extension-browser.webpack.config.js index 560a4bb021e..fd8e5877c3a 100644 --- a/extensions/typescript-language-features/extension-browser.webpack.config.js +++ b/extensions/typescript-language-features/extension-browser.webpack.config.js @@ -22,19 +22,24 @@ module.exports = withBrowserDefaults({ new CopyPlugin({ patterns: [ { - from: 'node_modules/typescript-web-server', - to: 'typescript-web', - transform: (content, absoluteFrom) => { - if (absoluteFrom.endsWith('tsserver.js')) { - return Terser.minify(content.toString()).code; - } - return content; + from: 'node_modules/typescript-web-server/*.d.ts', + to: 'typescript-web/', + flatten: true + }, + ], + }), + // @ts-ignore + new CopyPlugin({ + patterns: [ + { + from: 'node_modules/typescript-web-server/tsserver.js', + to: 'typescript-web/tsserver.web.js', + transform: (content) => { + return Terser.minify(content.toString()).code; + }, transformPath: (targetPath) => { - if (targetPath.endsWith('tsserver.js')) { - return targetPath.replace('tsserver.js', 'tsserver.web.js'); - } - return targetPath; + return targetPath.replace('tsserver.js', 'tsserver.web.js'); } } ], From 519ce367a3e5a552d88841e11b30614fa9c489dc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 24 Jul 2020 13:52:19 -0700 Subject: [PATCH 050/695] Use asAbsolutePath instead of hardcoding path --- .../typescript-language-features/src/extension.browser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/extension.browser.ts b/extensions/typescript-language-features/src/extension.browser.ts index f48f2b5071d..9291e22ae36 100644 --- a/extensions/typescript-language-features/src/extension.browser.ts +++ b/extensions/typescript-language-features/src/extension.browser.ts @@ -52,7 +52,7 @@ export function activate( const versionProvider = new StaticVersionProvider( new TypeScriptVersion( TypeScriptVersionSource.Bundled, - '/builtin-extension/typescript-language-features/dist/browser/typescript-web/tsserver.web.js', + context.asAbsolutePath('dist/browser/typescript-web/tsserver.web.js'), API.v400)); const lazyClientHost = createLazyClientHost(context, false, { From 86c04f72be7cb690f0304db4c4d4313abd5701c1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Fri, 24 Jul 2020 15:58:54 -0500 Subject: [PATCH 051/695] Fixes regression: cannot open image with special characters '#', '?', '%' (#102189) * Fixes #102188 * Add unit test for #102188 --- .../src/singlefolder-tests/webview.test.ts | 6 ++++++ .../vscode-api-tests/testWorkspace/image%02.png | Bin 0 -> 25587 bytes .../platform/webview/common/resourceLoader.ts | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 extensions/vscode-api-tests/testWorkspace/image%02.png 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 6177c155b52..f8e3d4fca9d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -272,6 +272,12 @@ suite('vscode API - webview', () => { const response = await sendRecieveMessage(webview, { src: imagePath.toString() }); assert.strictEqual(response.value, true); } + { + // #102188. Resource filename containing special characters like '%', '#', '?'. + const imagePath = webview.webview.asWebviewUri(workspaceFile('image%02.png')); + const response = await sendRecieveMessage(webview, { src: imagePath.toString() }); + assert.strictEqual(response.value, true); + } { const imagePath = webview.webview.asWebviewUri(workspaceFile('no-such-image.png')); const response = await sendRecieveMessage(webview, { src: imagePath.toString() }); diff --git a/extensions/vscode-api-tests/testWorkspace/image%02.png b/extensions/vscode-api-tests/testWorkspace/image%02.png new file mode 100644 index 0000000000000000000000000000000000000000..15b462975bee12491b059444010eeb347dc35fbd GIT binary patch literal 25587 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 literal 0 HcmV?d00001 diff --git a/src/vs/platform/webview/common/resourceLoader.ts b/src/vs/platform/webview/common/resourceLoader.ts index 22cc0e2e0c6..c38251c1f2a 100644 --- a/src/vs/platform/webview/common/resourceLoader.ts +++ b/src/vs/platform/webview/common/resourceLoader.ts @@ -99,7 +99,9 @@ function normalizeRequestPath(requestUri: URI) { // // vscode-webview-resource://id/scheme//authority?/path // - const resourceUri = URI.parse(requestUri.path.replace(/^\/([a-z0-9\-]+)(\/{1,2})/i, (_: string, scheme: string, sep: string) => { + + // Encode requestUri.path so that URI.parse can properly parse special characters like '#', '?', etc. + const resourceUri = URI.parse(encodeURIComponent(requestUri.path).replace(/%2F/g, '/').replace(/^\/([a-z0-9\-]+)\/{1,2}/i, (_: string, scheme: string, sep: string) => { if (sep.length === 1) { return `${scheme}:///`; // Add empty authority. } else { From f74e473238aca7b79c08be761d99a0232838ca4c Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 24 Jul 2020 14:06:09 -0700 Subject: [PATCH 052/695] fixes #103167 --- src/vs/base/browser/dom.ts | 10 ++++++++++ src/vs/base/browser/ui/actionbar/actionbar.ts | 4 ++-- src/vs/base/browser/ui/contextview/contextview.ts | 2 +- src/vs/base/browser/ui/menu/menu.ts | 8 ++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index d5622349ff0..f203119f8f6 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -749,6 +749,16 @@ export function getShadowRoot(domNode: Node): ShadowRoot | null { return isShadowRoot(domNode) ? domNode : null; } +export function getActiveElement(): Element | null { + let result = document.activeElement; + + while (result?.shadowRoot) { + result = result.shadowRoot.activeElement; + } + + return result; +} + export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement { let style = document.createElement('style'); style.type = 'text/css'; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 0044d786b6a..bbc0918db24 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -173,7 +173,7 @@ export class ActionBar extends Disposable implements IActionRunner { this.focusTracker = this._register(DOM.trackFocus(this.domNode)); this._register(this.focusTracker.onDidBlur(() => { - if (document.activeElement === this.domNode || !DOM.isAncestor(document.activeElement, this.domNode)) { + if (DOM.getActiveElement() === this.domNode || !DOM.isAncestor(DOM.getActiveElement(), this.domNode)) { this._onDidBlur.fire(); this.focusedItem = undefined; } @@ -214,7 +214,7 @@ export class ActionBar extends Disposable implements IActionRunner { private updateFocusedItem(): void { for (let i = 0; i < this.actionsList.children.length; i++) { const elem = this.actionsList.children[i]; - if (DOM.isAncestor(document.activeElement, elem)) { + if (DOM.isAncestor(DOM.getActiveElement(), elem)) { this.focusedItem = i; break; } diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 0863191afd2..65806e0af74 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -156,7 +156,7 @@ export class ContextView extends Disposable { if (this.useShadowDOM) { this.shadowRootHostElement = DOM.$('.shadow-root-host'); this.container.appendChild(this.shadowRootHostElement); - this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'closed' }); + this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = `