From 1b5217985b86793d558a6d107099247570c8b557 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 May 2020 17:35:39 -0700 Subject: [PATCH 001/148] notebook document creation in UI side --- .../src/notebookTestMain.ts | 29 ++- .../api/browser/mainThreadNotebook.ts | 100 ++++++--- .../workbench/api/common/extHost.protocol.ts | 27 ++- .../workbench/api/common/extHostNotebook.ts | 195 ++++++++++++++---- .../notebook/browser/notebookEditorInput.ts | 4 + .../notebook/browser/notebookServiceImpl.ts | 45 +++- .../common/model/notebookTextModel.ts | 48 +++++ .../contrib/notebook/common/notebookCommon.ts | 9 +- .../notebook/common/notebookEditorModel.ts | 63 +++++- .../notebook/common/notebookService.ts | 9 +- .../api/extHostNotebookConcatDocument.test.ts | 6 +- 11 files changed, 442 insertions(+), 93 deletions(-) diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/extensions/vscode-notebook-tests/src/notebookTestMain.ts index b27f6760475..e5402feb298 100644 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookTestMain.ts @@ -6,12 +6,22 @@ import * as vscode from 'vscode'; export function activate(context: vscode.ExtensionContext): any { - context.subscriptions.push(vscode.notebook.registerNotebookProvider('notebookCoreTest', { - resolveNotebook: async (editor: vscode.NotebookEditor) => { - await editor.edit(eb => { - eb.insert(0, 'test', 'typescript', vscode.CellKind.Code, [], {}); - }); - return; + context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', { + onDidChangeNotebook: new vscode.EventEmitter().event, + openNotebook: async (_resource: vscode.Uri) => { + return { + languages: ['typescript'], + metadata: {}, + cells: [ + { + source: 'test', + language: 'typescript', + cellKind: vscode.CellKind.Code, + outputs: [], + metadata: {} + } + ] + }; }, executeCell: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell | undefined, _token: vscode.CancellationToken) => { if (!_cell) { @@ -26,8 +36,11 @@ export function activate(context: vscode.ExtensionContext): any { }]; return; }, - save: async (_document: vscode.NotebookDocument) => { - return true; + saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; + }, + saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; } })); } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 4653ef3ffec..031d901d8c5 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -84,7 +84,9 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo registerListeners() { this._register(this._notebookService.onDidChangeActiveEditor(e => { - this._proxy.$updateActiveEditor(e.viewType, e.uri); + this._proxy.$acceptDocumentAndEditorsDelta({ + newActiveEditor: e.uri + }); })); const updateOrder = () => { @@ -116,8 +118,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._notebookService.unregisterNotebookRenderer(handle); } - async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise { - let controller = new MainThreadNotebookController(this._proxy, this, viewType); + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, v2: boolean): Promise { + let controller = new MainThreadNotebookController(this._proxy, this, viewType, v2); this._notebookProviders.set(viewType, controller); this._notebookService.registerNotebookController(viewType, extension, controller); return; @@ -129,11 +131,11 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return; } - async $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { + async $_deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { let controller = this._notebookProviders.get(viewType); if (controller) { - controller.createNotebookDocument(handle, viewType, resource); + controller._deprecated_createNotebookDocument(handle, viewType, resource); } return; @@ -163,8 +165,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } } - async resolveNotebook(viewType: string, uri: URI): Promise { - let handle = await this._proxy.$resolveNotebook(viewType, uri); + async _deprecated_resolveNotebook(viewType: string, uri: URI): Promise { + let handle = await this._proxy.$_deprecated_resolveNotebook(viewType, uri); return handle; } @@ -195,15 +197,50 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo export class MainThreadNotebookController implements IMainNotebookController { private _mapping: Map = new Map(); + static documentHandle: number = 0; constructor( private readonly _proxy: ExtHostNotebookShape, private _mainThreadNotebook: MainThreadNotebooks, - private _viewType: string + private _viewType: string, + public readonly v2: boolean ) { } - async resolveNotebook(viewType: string, uri: URI): Promise { + async createNotebook(viewType: string, uri: URI, forBackup: boolean): Promise { + let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); + + if (mainthreadNotebook) { + return mainthreadNotebook.textModel; + } + + let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri); + await this.createNotebookDocument(document.handle, document.viewType, document.uri); + + if (forBackup) { + return document.textModel; + } + + // open notebook document + const data = await this._proxy.$resolveNotebookData(viewType, uri); + if (!data) { + return; + } + + document.textModel.languages = data.languages; + document.textModel.metadata = data.metadata; + document.textModel.applyEdit(document.textModel.versionId, [ + { + editType: CellEditType.Insert, + index: 0, + cells: data!.cells + } + ]); + + return document.textModel; + } + + async _deprecated_resolveNotebook(viewType: string, uri: URI): Promise { // TODO: resolve notebook should wait for all notebook document destory operations to finish. let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); @@ -211,7 +248,7 @@ export class MainThreadNotebookController implements IMainNotebookController { return mainthreadNotebook.textModel; } - let notebookHandle = await this._mainThreadNotebook.resolveNotebook(viewType, uri); + let notebookHandle = await this._mainThreadNotebook._deprecated_resolveNotebook(viewType, uri); if (notebookHandle !== undefined) { mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) { @@ -250,10 +287,35 @@ export class MainThreadNotebookController implements IMainNotebookController { this._proxy.$onDidReceiveMessage(uri, message); } - // Methods for ExtHost async createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); this._mapping.set(URI.revive(resource).toString(), document); + + await this._proxy.$acceptDocumentAndEditorsDelta({ + addedDocuments: [{ + viewType: viewType, + handle: document.handle, + uri: resource + }] + }); + } + + async removeNotebookDocument(notebook: INotebookTextModel): Promise { + let document = this._mapping.get(URI.from(notebook.uri).toString()); + + if (!document) { + return; + } + + await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] }); + document.dispose(); + this._mapping.delete(URI.from(notebook.uri).toString()); + } + + // Methods for ExtHost + async _deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { + let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); + this._mapping.set(URI.revive(resource).toString(), document); } updateLanguages(resource: UriComponents, languages: string[]) { @@ -280,20 +342,6 @@ export class MainThreadNotebookController implements IMainNotebookController { return this._proxy.$executeNotebook(this._viewType, uri, handle, token); } - async destoryNotebookDocument(notebook: INotebookTextModel): Promise { - let document = this._mapping.get(URI.from(notebook.uri).toString()); - - if (!document) { - return; - } - - let removeFromExtHost = await this._proxy.$destoryNotebookDocument(this._viewType, notebook.uri); - if (removeFromExtHost) { - document.dispose(); - this._mapping.delete(URI.from(notebook.uri).toString()); - } - } - async save(uri: URI, token: CancellationToken): Promise { return this._proxy.$saveNotebook(this._viewType, uri, token); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c6b3c31999d..942e16bc90c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -687,11 +687,11 @@ export type NotebookCellOutputsSplice = [ ]; export interface MainThreadNotebookShape extends IDisposable { - $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise; + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, v2: boolean): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise; $unregisterNotebookRenderer(handle: number): Promise; - $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise; + $_deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise; @@ -1538,16 +1538,31 @@ export interface INotebookEditorPropertiesChangeData { selections: INotebookSelectionChangeEvent | null; } +export interface INotebookModelAddedData { + uri: UriComponents; + handle: number; + // versionId: number; + viewType: string; +} + +export interface INotebookDocumentsAndEditorsDelta { + removedDocuments?: UriComponents[]; + addedDocuments?: INotebookModelAddedData[]; + // removedEditors?: string[]; + // addedEditors?: ITextEditorAddData[]; + newActiveEditor?: UriComponents | null; +} + export interface ExtHostNotebookShape { - $resolveNotebook(viewType: string, uri: UriComponents): Promise; + $resolveNotebookData(viewType: string, uri: UriComponents): Promise; + $_deprecated_resolveNotebook(viewType: string, uri: UriComponents): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; - $updateActiveEditor(viewType: string, uri: UriComponents): Promise; - $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $onDidReceiveMessage(uri: UriComponents, message: any): void; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; $acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void; + $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise; } export interface ExtHostStorageShape { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 1b1d7db1f80..2b8e61ca2c0 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -10,10 +10,10 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } 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, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Disposable as VSCodeDisposable } from './extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; @@ -717,7 +717,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } this._notebookProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, false); return new VSCodeDisposable(() => { this._notebookProviders.delete(viewType); this._proxy.$unregisterNotebookProvider(viewType); @@ -735,7 +735,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } this._notebookContentProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, true); return new VSCodeDisposable(() => { this._notebookContentProviders.delete(viewType); this._proxy.$unregisterNotebookProvider(viewType); @@ -749,7 +749,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const revivedUri = URI.revive(uri); if (!this._documents.has(revivedUri.toString())) { let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); - await this._proxy.$createNotebookDocument( + await this._proxy.$_deprecated_createNotebookDocument( document.handle, viewType, uri @@ -793,7 +793,104 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } - async $resolveNotebook(viewType: string, uri: UriComponents): Promise { + async $resolveNotebookData(viewType: string, uri: UriComponents): Promise { + let provider = this._notebookContentProviders.get(viewType); + let document = this._documents.get(URI.revive(uri).toString()); + + if (provider && document) { + const rawCells = await provider.provider.openNotebook(URI.revive(uri)); + const renderers = new Set(); + const dto = { + metadata: { + ...notebookDocumentMetadataDefaults, + ...rawCells.metadata + }, + languages: rawCells.languages, + cells: rawCells.cells.map(cell => { + let transformedOutputs = cell.outputs.map(output => { + if (output.outputKind === CellOutputKind.Rich) { + // TODO display string[] + const ret = this._transformMimeTypes(document!, (rawCells.metadata.displayOrder as string[]) || [], output); + + if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { + renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); + } + return ret; + } else { + return output as IStreamOutput | IErrorOutput; + } + }); + + return { + language: cell.language, + cellKind: cell.cellKind, + metadata: cell.metadata, + source: cell.source, + outputs: transformedOutputs + }; + }) + }; + + return dto; + } + + return; + } + + private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { + let mimeTypes = Object.keys(output.data); + + // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. + let coreDisplayOrder = this.outputDisplayOrder; + const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []); + + let orderMimeTypes: IOrderedMimeType[] = []; + + sorted.forEach(mimeType => { + let handlers = this.findBestMatchedRenderer(mimeType); + + if (handlers.length) { + let renderedOutput = handlers[0].render(document, output, mimeType); + + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: true, + rendererId: handlers[0].handle, + output: renderedOutput + }); + + for (let i = 1; i < handlers.length; i++) { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false, + rendererId: handlers[i].handle + }); + } + + if (mimeTypeSupportedByCore(mimeType)) { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false, + rendererId: -1 + }); + } + } else { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false + }); + } + }); + + return { + outputKind: output.outputKind, + data: output.data, + orderedMimeTypes: orderMimeTypes, + pickedMimeTypeIndex: 0 + }; + } + + async $_deprecated_resolveNotebook(viewType: string, uri: UriComponents): Promise { let notebookFromNotebookContentProvider = await this._resolveNotebookFromContentProvider(viewType, uri); if (notebookFromNotebookContentProvider !== undefined) { @@ -805,7 +902,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN if (provider) { if (!this._documents.has(URI.revive(uri).toString())) { let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, URI.revive(uri), this); - await this._proxy.$createNotebookDocument( + await this._proxy.$_deprecated_createNotebookDocument( document.handle, viewType, uri @@ -883,37 +980,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return false; } - async $updateActiveEditor(viewType: string, uri: UriComponents): Promise { - this._activeNotebookDocument = this._documents.get(URI.revive(uri).toString()); - this._activeNotebookEditor = this._editors.get(URI.revive(uri).toString())?.editor; - } - - async $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise { - let provider = this._notebookProviders.get(viewType); - - if (!provider) { - return false; - } - - let document = this._documents.get(URI.revive(uri).toString()); - - if (document) { - document.dispose(); - this._documents.delete(URI.revive(uri).toString()); - this._onDidCloseNotebookDocument.fire(document); - } - - let editor = this._editors.get(URI.revive(uri).toString()); - - if (editor) { - editor.editor.dispose(); - editor.onDidReceiveMessage.dispose(); - this._editors.delete(URI.revive(uri).toString()); - } - - return true; - } - $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void { this._outputDisplayOrder = displayOrder; } @@ -957,4 +1023,57 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } } + + async $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta) { + if (delta.removedDocuments) { + delta.removedDocuments.forEach((uri) => { + let document = this._documents.get(URI.revive(uri).toString()); + + if (document) { + document.dispose(); + this._documents.delete(URI.revive(uri).toString()); + this._onDidCloseNotebookDocument.fire(document); + } + + let editor = this._editors.get(URI.revive(uri).toString()); + + if (editor) { + editor.editor.dispose(); + editor.onDidReceiveMessage.dispose(); + this._editors.delete(URI.revive(uri).toString()); + } + }); + } + + if (delta.addedDocuments) { + delta.addedDocuments.forEach(modelData => { + const revivedUri = URI.revive(modelData.uri); + const viewType = modelData.viewType; + if (!this._documents.has(revivedUri.toString())) { + let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); + this._documents.set(revivedUri.toString(), document); + } + + const onDidReceiveMessage = new Emitter(); + + let editor = new ExtHostNotebookEditor( + viewType, + `${ExtHostNotebookController._handlePool++}`, + revivedUri, + this._proxy, + onDidReceiveMessage, + this._documents.get(revivedUri.toString())!, + this._documentsAndEditors + ); + + // TODO, does it already exist? + this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage }); + }); + } + + if (delta.newActiveEditor) { + this._activeNotebookDocument = this._documents.get(URI.revive(delta.newActiveEditor).toString()); + this._activeNotebookEditor = this._editors.get(URI.revive(delta.newActiveEditor).toString())?.editor; + } + } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 088143de4db..c84e0cbd255 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -96,6 +96,10 @@ export class NotebookEditorInput extends EditorInput { this._onDidChangeDirty.fire(); })); + if (this.textModel.isDirty()) { + this._onDidChangeDirty.fire(); + } + return this.textModel; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 8e8a3d0c741..7c625ddac98 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -11,7 +11,7 @@ import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from ' import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, CellEditType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { Iterable } from 'vs/base/common/iterator'; @@ -202,13 +202,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu return; } - async resolveNotebook(viewType: string, uri: URI): Promise { + async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, cells: ICellDto2[]): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; } - const notebookModel = await provider.controller.resolveNotebook(viewType, uri); + const notebookModel = await provider.controller.createNotebook(viewType, uri, true); if (!notebookModel) { return undefined; } @@ -219,6 +219,43 @@ export class NotebookService extends Disposable implements INotebookService, ICu notebookModel, (model) => this._onWillDispose(model), ); + this._models[modelId] = modelData; + + notebookModel.metadata = metadata; + notebookModel.languages = []; + + notebookModel.applyEdit(notebookModel.versionId, [ + { + editType: CellEditType.Insert, + index: 0, + cells: cells + } + ]); + + return modelData.model; + } + + async resolveNotebook(viewType: string, uri: URI): Promise { + const provider = this._notebookProviders.get(viewType); + if (!provider) { + return undefined; + } + + let notebookModel: NotebookTextModel | undefined; + + if (provider.controller.v2) { + notebookModel = await provider.controller.createNotebook(viewType, uri, false); + } else { + notebookModel = await provider.controller._deprecated_resolveNotebook(viewType, uri); + } + + // new notebook model created + const modelId = MODEL_ID(uri); + const modelData = new ModelData( + notebookModel!, + (model) => this._onWillDispose(model), + ); + this._models[modelId] = modelData; return modelData.model; } @@ -265,7 +302,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu let provider = this._notebookProviders.get(viewType); if (provider) { - provider.controller.destoryNotebookDocument(notebook); + provider.controller.removeNotebookDocument(notebook); } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 4c5ca6ecf6b..80eef379624 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -8,6 +8,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ITextSnapshot } from 'vs/editor/common/model'; function compareRangesUsingEnds(a: [number, number], b: [number, number]): number { if (a[1] === b[1]) { @@ -17,6 +18,49 @@ function compareRangesUsingEnds(a: [number, number], b: [number, number]): numbe return a[1] - b[1]; } +export class NotebookTextModelSnapshot implements ITextSnapshot { + // private readonly _pieces: Ce[] = []; + private _index: number = -1; + + constructor(private _model: NotebookTextModel) { + // for (let i = 0; i < this._model.cells.length; i++) { + // const cell = this._model.cells[i]; + // this._pieces.push(this._model.cells[i].textBuffer.createSnapshot(true)); + // } + } + + read(): string | null { + + if (this._index === -1) { + this._index++; + return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "cells": [`; + } + + if (this._index < this._model.cells.length) { + const cell = this._model.cells[this._index]; + + const data = { + source: cell.getValue(), + metadata: cell.metadata, + cellKind: cell.cellKind, + language: cell.language + }; + + const rawStr = JSON.stringify(data); + const isLastCell = this._index === this._model.cells.length - 1; + + this._index++; + return isLastCell ? rawStr : (rawStr + ','); + } else if (this._index === this._model.cells.length) { + this._index++; + return `]}`; + } else { + return null; + } + } + +} + export class NotebookTextModel extends Disposable implements INotebookTextModel { private static _cellhandlePool: number = 0; @@ -142,6 +186,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return true; } + createSnapshot(preserveBOM?: boolean): ITextSnapshot { + return new NotebookTextModelSnapshot(this); + } + private _increaseVersionId(): void { this._versionId = this._versionId + 1; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index b2a349dd607..90006f52f2a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -273,7 +273,7 @@ export enum CellEditType { } export interface ICellDto2 { - source: string[]; + source: string | string[]; language: string; cellKind: CellKind; outputs: IOutput[]; @@ -300,6 +300,13 @@ export interface INotebookEditData { renderers: number[]; } +export interface NotebookDataDto { + readonly cells: ICellDto2[]; + readonly languages: string[]; + readonly metadata: NotebookDocumentMetadata; +} + + export namespace CellUri { export const scheme = 'vscode-notebook'; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index fd66b9a06b7..b6ab7c37ce2 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -15,6 +15,8 @@ import { URI } from 'vs/base/common/uri'; import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { basename } from 'vs/base/common/resources'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { DefaultEndOfLine, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model'; export interface INotebookEditorModelManager { models: NotebookEditorModel[]; @@ -47,7 +49,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN public readonly resource: URI, public readonly viewType: string, @INotebookService private readonly notebookService: INotebookService, - @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IBackupFileService private readonly backupFileService: IBackupFileService ) { super(); this._register(this.workingCopyService.registerWorkingCopy(this)); @@ -56,7 +59,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN capabilities = 0; async backup(): Promise { - return {}; + return { content: this._notebook.createSnapshot(true) }; } async revert(options?: IRevertOptions | undefined): Promise { @@ -64,20 +67,72 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } async load(): Promise { + if (this.isResolved()) { + return this; + } + + const backup = await this.backupFileService.resolve(this.resource); + + if (this.isResolved()) { + return this; // Make sure meanwhile someone else did not succeed in loading + } + + if (backup) { + try { + return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF)); + } catch (error) { + // this.logService.error('[text file model] load() from backup', error); // ignore error and continue to load as file below + } + } + + return this.loadFromProvider(); + } + + private async loadFromBackup(content: ITextBuffer): Promise { + const fullRange = content.getRangeAt(0, content.getLength()); + const data = JSON.parse(content.getValueInRange(fullRange, EndOfLinePreference.LF)); + + const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.cells); + this._notebook = notebook!; + + this._name = basename(this._notebook!.uri); + + this._register(this._notebook.onDidChangeContent(() => { + this.setDirty(true); + this._onDidChangeContent.fire(); + })); + + await this.backupFileService.discardBackup(this.resource); + this.setDirty(true); + + return this; + } + + private async loadFromProvider() { const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource); this._notebook = notebook!; this._name = basename(this._notebook!.uri); this._register(this._notebook.onDidChangeContent(() => { - this._dirty = true; - this._onDidChangeDirty.fire(); + this.setDirty(true); this._onDidChangeContent.fire(); })); return this; } + isResolved(): boolean { + return !!this._notebook; + } + + setDirty(newState: boolean) { + if (this._dirty !== newState) { + this._dirty = newState; + this._onDidChangeDirty.fire(); + } + } + isDirty() { return this._dirty; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index f846f30ffe8..767d4e617c3 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -17,11 +17,13 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { - resolveNotebook(viewType: string, uri: URI): Promise; + v2: boolean; + createNotebook(viewType: string, uri: URI, fromBackup: boolean): Promise; + _deprecated_resolveNotebook(viewType: string, uri: URI): Promise; executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; onDidReceiveMessage(uri: URI, message: any): void; executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; - destoryNotebookDocument(notebook: INotebookTextModel): Promise; + removeNotebookDocument(notebook: INotebookTextModel): Promise; save(uri: URI, token: CancellationToken): Promise; } @@ -36,6 +38,7 @@ export interface INotebookService { unregisterNotebookRenderer(handle: number): void; getRendererInfo(handle: number): INotebookRendererInfo | undefined; resolveNotebook(viewType: string, uri: URI): Promise; + createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, cells: ICellDto2[]): Promise; executeNotebook(viewType: string, uri: URI): Promise; executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 0b0827adb8a..67f9b53da4e 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -41,7 +41,7 @@ suite('NotebookConcatDocument', function () { rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { async $registerNotebookProvider() { } async $unregisterNotebookProvider() { } - async $createNotebookDocument() { } + async $_deprecated_createNotebookDocument() { } }); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); @@ -49,7 +49,7 @@ suite('NotebookConcatDocument', function () { let reg = extHostNotebooks.registerNotebookProvider(nullExtensionDescription, 'test', new class extends mock() { async resolveNotebook() { } }); - await extHostNotebooks.$resolveNotebook('test', notebookUri); + await extHostNotebooks.$_deprecated_resolveNotebook('test', notebookUri); extHostNotebooks.$acceptModelChanged(notebookUri, { kind: NotebookCellsChangeType.ModelChange, versionId: 0, @@ -62,7 +62,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }]]] }); - await extHostNotebooks.$updateActiveEditor('test', notebookUri); + await extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: notebookUri }); notebook = extHostNotebooks.activeNotebookDocument!; From cb7ea6e166dcfb9cecacdd9560e8dbaa292a341f Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 May 2020 18:37:54 -0700 Subject: [PATCH 002/148] Remove notebook content provider --- src/vs/vscode.proposed.d.ts | 11 -- .../api/browser/mainThreadNotebook.ts | 69 ++-------- .../workbench/api/common/extHost.api.impl.ts | 4 - .../workbench/api/common/extHost.protocol.ts | 4 +- .../workbench/api/common/extHostNotebook.ts | 130 +----------------- .../notebook/browser/notebookServiceImpl.ts | 10 +- .../common/model/notebookTextModel.ts | 13 +- .../notebook/common/notebookEditorModel.ts | 2 +- .../notebook/common/notebookService.ts | 6 +- .../api/extHostNotebookConcatDocument.test.ts | 13 +- 10 files changed, 42 insertions(+), 220 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 2b42ca4451f..1f50a14b528 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1737,12 +1737,6 @@ declare module 'vscode' { edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; } - export interface NotebookProvider { - resolveNotebook(editor: NotebookEditor): Promise; - executeCell(document: NotebookDocument, cell: NotebookCell | undefined, token: CancellationToken): Promise; - save(document: NotebookDocument): Promise; - } - export interface NotebookOutputSelector { type: string; subTypes?: string[]; @@ -1805,11 +1799,6 @@ declare module 'vscode' { provider: NotebookContentProvider ): Disposable; - export function registerNotebookProvider( - notebookType: string, - provider: NotebookProvider - ): Disposable; - export function registerNotebookOutputRenderer( type: string, outputSelector: NotebookOutputSelector, diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 031d901d8c5..8cafccf0955 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -118,8 +118,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._notebookService.unregisterNotebookRenderer(handle); } - async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, v2: boolean): Promise { - let controller = new MainThreadNotebookController(this._proxy, this, viewType, v2); + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise { + let controller = new MainThreadNotebookController(this._proxy, this, viewType); this._notebookProviders.set(viewType, controller); this._notebookService.registerNotebookController(viewType, extension, controller); return; @@ -131,16 +131,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return; } - async $_deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { - let controller = this._notebookProviders.get(viewType); - - if (controller) { - controller._deprecated_createNotebookDocument(handle, viewType, resource); - } - - return; - } - async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise { let controller = this._notebookProviders.get(viewType); @@ -165,11 +155,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } } - async _deprecated_resolveNotebook(viewType: string, uri: URI): Promise { - let handle = await this._proxy.$_deprecated_resolveNotebook(viewType, uri); - return handle; - } - async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { let controller = this._notebookProviders.get(viewType); controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers); @@ -202,8 +187,7 @@ export class MainThreadNotebookController implements IMainNotebookController { constructor( private readonly _proxy: ExtHostNotebookShape, private _mainThreadNotebook: MainThreadNotebooks, - private _viewType: string, - public readonly v2: boolean + private _viewType: string ) { } @@ -215,7 +199,7 @@ export class MainThreadNotebookController implements IMainNotebookController { } let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri); - await this.createNotebookDocument(document.handle, document.viewType, document.uri); + await this.createNotebookDocument(document); if (forBackup) { return document.textModel; @@ -229,39 +213,11 @@ export class MainThreadNotebookController implements IMainNotebookController { document.textModel.languages = data.languages; document.textModel.metadata = data.metadata; - document.textModel.applyEdit(document.textModel.versionId, [ - { - editType: CellEditType.Insert, - index: 0, - cells: data!.cells - } - ]); + document.textModel.initialize(data!.cells); return document.textModel; } - async _deprecated_resolveNotebook(viewType: string, uri: URI): Promise { - // TODO: resolve notebook should wait for all notebook document destory operations to finish. - let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); - - if (mainthreadNotebook) { - return mainthreadNotebook.textModel; - } - - let notebookHandle = await this._mainThreadNotebook._deprecated_resolveNotebook(viewType, uri); - if (notebookHandle !== undefined) { - mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); - if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) { - // it's empty, we should create an empty template one - const mainCell = mainthreadNotebook.textModel.createCellTextModel([''], mainthreadNotebook.textModel.languages.length ? mainthreadNotebook.textModel.languages[0] : '', CellKind.Code, [], undefined); - mainthreadNotebook.textModel.insertTemplateCell(mainCell); - } - return mainthreadNotebook?.textModel; - } - - return undefined; - } - async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); @@ -287,15 +243,14 @@ export class MainThreadNotebookController implements IMainNotebookController { this._proxy.$onDidReceiveMessage(uri, message); } - async createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { - let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); - this._mapping.set(URI.revive(resource).toString(), document); + async createNotebookDocument(document: MainThreadNotebookDocument): Promise { + this._mapping.set(document.uri.toString(), document); await this._proxy.$acceptDocumentAndEditorsDelta({ addedDocuments: [{ - viewType: viewType, + viewType: document.viewType, handle: document.handle, - uri: resource + uri: document.uri }] }); } @@ -313,10 +268,6 @@ export class MainThreadNotebookController implements IMainNotebookController { } // Methods for ExtHost - async _deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { - let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); - this._mapping.set(URI.revive(resource).toString(), document); - } updateLanguages(resource: UriComponents, languages: string[]) { let document = this._mapping.get(URI.from(resource).toString()); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index ca1efcab306..44915fff5dd 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -910,10 +910,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidCloseNotebookDocument; }, - registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => { - checkProposedApiEnabled(extension); - return extHostNotebook.registerNotebookProvider(extension, viewType, provider); - }, registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 942e16bc90c..4676efcffe5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -687,11 +687,10 @@ export type NotebookCellOutputsSplice = [ ]; export interface MainThreadNotebookShape extends IDisposable { - $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, v2: boolean): Promise; + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise; $unregisterNotebookRenderer(handle: number): Promise; - $_deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise; @@ -1555,7 +1554,6 @@ export interface INotebookDocumentsAndEditorsDelta { export interface ExtHostNotebookShape { $resolveNotebookData(viewType: string, uri: UriComponents): Promise; - $_deprecated_resolveNotebook(viewType: string, uri: UriComponents): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 2b8e61ca2c0..7a62a3e7ed0 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -625,7 +625,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private static _handlePool: number = 0; private readonly _proxy: MainThreadNotebookShape; - private readonly _notebookProviders = new Map(); private readonly _notebookContentProviders = new Map(); private readonly _documents = new Map(); private readonly _editors = new Map; }>(); @@ -706,93 +705,24 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return matches; } - registerNotebookProvider( - extension: IExtensionDescription, - viewType: string, - provider: vscode.NotebookProvider, - ): vscode.Disposable { - - if (this._notebookProviders.has(viewType)) { - throw new Error(`Notebook provider for '${viewType}' already registered`); - } - - this._notebookProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, false); - return new VSCodeDisposable(() => { - this._notebookProviders.delete(viewType); - this._proxy.$unregisterNotebookProvider(viewType); - }); - } - registerNotebookContentProvider( extension: IExtensionDescription, viewType: string, provider: vscode.NotebookContentProvider, ): vscode.Disposable { - if (this._notebookProviders.has(viewType)) { + if (this._notebookContentProviders.has(viewType)) { throw new Error(`Notebook provider for '${viewType}' already registered`); } this._notebookContentProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, true); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); return new VSCodeDisposable(() => { this._notebookContentProviders.delete(viewType); this._proxy.$unregisterNotebookProvider(viewType); }); } - async _resolveNotebookFromContentProvider(viewType: string, uri: UriComponents): Promise { - let provider = this._notebookContentProviders.get(viewType); - - if (provider) { - const revivedUri = URI.revive(uri); - if (!this._documents.has(revivedUri.toString())) { - let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); - await this._proxy.$_deprecated_createNotebookDocument( - document.handle, - viewType, - uri - ); - - this._documents.set(revivedUri.toString(), document); - } - - const onDidReceiveMessage = new Emitter(); - - let editor = new ExtHostNotebookEditor( - viewType, - `${ExtHostNotebookController._handlePool++}`, - revivedUri, - this._proxy, - onDidReceiveMessage, - this._documents.get(revivedUri.toString())!, - this._documentsAndEditors - ); - - this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage }); - - const data = await provider.provider.openNotebook(revivedUri); - editor.document.languages = data.languages; - editor.document.metadata = { - ...notebookDocumentMetadataDefaults, - ...data.metadata - }; - - await editor.edit(editBuilder => { - for (let i = 0; i < data.cells.length; i++) { - const cell = data.cells[i]; - editBuilder.insert(0, cell.source, cell.language, cell.cellKind, cell.outputs, cell.metadata); - } - }); - - this._onDidOpenNotebookDocument.fire(editor.document); - return editor.document.handle; - } else { - return Promise.resolve(undefined); - } - } - async $resolveNotebookData(viewType: string, uri: UriComponents): Promise { let provider = this._notebookContentProviders.get(viewType); let document = this._documents.get(URI.revive(uri).toString()); @@ -890,48 +820,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }; } - async $_deprecated_resolveNotebook(viewType: string, uri: UriComponents): Promise { - let notebookFromNotebookContentProvider = await this._resolveNotebookFromContentProvider(viewType, uri); - - if (notebookFromNotebookContentProvider !== undefined) { - return notebookFromNotebookContentProvider; - } - - let provider = this._notebookProviders.get(viewType); - - if (provider) { - if (!this._documents.has(URI.revive(uri).toString())) { - let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, URI.revive(uri), this); - await this._proxy.$_deprecated_createNotebookDocument( - document.handle, - viewType, - uri - ); - - this._documents.set(URI.revive(uri).toString(), document); - } - - const onDidReceiveMessage = new Emitter(); - - let editor = new ExtHostNotebookEditor( - viewType, - `${ExtHostNotebookController._handlePool++}`, - URI.revive(uri), - this._proxy, - onDidReceiveMessage, - this._documents.get(URI.revive(uri).toString())!, - this._documentsAndEditors - ); - - this._editors.set(URI.revive(uri).toString(), { editor, onDidReceiveMessage }); - await provider.provider.resolveNotebook(editor); - // await editor.document.$updateCells(); - return editor.document.handle; - } - - return Promise.resolve(undefined); - } - async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise { let document = this._documents.get(URI.revive(uri).toString()); @@ -944,15 +832,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return this._notebookContentProviders.get(viewType)!.provider.executeCell(document, cell, token); } - - let provider = this._notebookProviders.get(viewType); - - if (!provider) { - return; - } - - let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; - return provider.provider.executeCell(document!, cell, token); } async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise { @@ -971,10 +850,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return true; } - let provider = this._notebookProviders.get(viewType); + let provider = this._notebookContentProviders.get(viewType); if (provider && document) { - return await provider.provider.save(document); + await provider.provider.saveNotebook(document, token); + return true; } return false; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 7c625ddac98..30dbe298b03 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -202,7 +202,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return; } - async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, cells: ICellDto2[]): Promise { + async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; @@ -222,7 +222,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._models[modelId] = modelData; notebookModel.metadata = metadata; - notebookModel.languages = []; + notebookModel.languages = languages; notebookModel.applyEdit(notebookModel.versionId, [ { @@ -243,11 +243,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu let notebookModel: NotebookTextModel | undefined; - if (provider.controller.v2) { - notebookModel = await provider.controller.createNotebook(viewType, uri, false); - } else { - notebookModel = await provider.controller._deprecated_resolveNotebook(viewType, uri); - } + notebookModel = await provider.controller.createNotebook(viewType, uri, false); // new notebook model created const modelId = MODEL_ID(uri); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 80eef379624..fce799cc033 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextSnapshot } from 'vs/editor/common/model'; function compareRangesUsingEnds(a: [number, number], b: [number, number]): number { @@ -33,7 +33,7 @@ export class NotebookTextModelSnapshot implements ITextSnapshot { if (this._index === -1) { this._index++; - return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "cells": [`; + return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "languages": ${JSON.stringify(this._model.languages)}, "cells": [`; } if (this._index < this._model.cells.length) { @@ -121,6 +121,15 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return new NotebookCellTextModel(cellUri, cellHandle, source, language, cellKind, outputs || [], metadata); } + initialize(cells: ICellDto2[]) { + const mainCells = cells.map(cell => { + const cellHandle = NotebookTextModel._cellhandlePool++; + const cellUri = CellUri.generate(this.uri, cellHandle); + return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata); + }); + this.insertNewCell(0, mainCells); + } + applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[]): boolean { if (modelVersionId !== this._versionId) { return false; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index b6ab7c37ce2..1ab0ecb3ea5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -92,7 +92,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN const fullRange = content.getRangeAt(0, content.getLength()); const data = JSON.parse(content.getValueInRange(fullRange, EndOfLinePreference.LF)); - const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.cells); + const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.languages, data.cells); this._notebook = notebook!; this._name = basename(this._notebook!.uri); diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 767d4e617c3..2d92cd67e51 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -17,9 +17,7 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { - v2: boolean; - createNotebook(viewType: string, uri: URI, fromBackup: boolean): Promise; - _deprecated_resolveNotebook(viewType: string, uri: URI): Promise; + createNotebook(viewType: string, uri: URI, forBackup: boolean): Promise; executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; onDidReceiveMessage(uri: URI, message: any): void; executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; @@ -38,7 +36,7 @@ export interface INotebookService { unregisterNotebookRenderer(handle: number): void; getRendererInfo(handle: number): INotebookRendererInfo | undefined; resolveNotebook(viewType: string, uri: URI): Promise; - createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, cells: ICellDto2[]): Promise; + createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise; executeNotebook(viewType: string, uri: URI): Promise; executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 67f9b53da4e..7695ee2df5b 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -41,15 +41,20 @@ suite('NotebookConcatDocument', function () { rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { async $registerNotebookProvider() { } async $unregisterNotebookProvider() { } - async $_deprecated_createNotebookDocument() { } }); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors); - let reg = extHostNotebooks.registerNotebookProvider(nullExtensionDescription, 'test', new class extends mock() { - async resolveNotebook() { } + let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock() { + // async openNotebook() { } + }); + await extHostNotebooks.$acceptDocumentAndEditorsDelta({ + addedDocuments: [{ + handle: 0, + uri: notebookUri, + viewType: 'test' + }] }); - await extHostNotebooks.$_deprecated_resolveNotebook('test', notebookUri); extHostNotebooks.$acceptModelChanged(notebookUri, { kind: NotebookCellsChangeType.ModelChange, versionId: 0, From 3c710dfcc996957aa3e12bccc174ce7af1df03fa Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 May 2020 18:47:18 -0700 Subject: [PATCH 003/148] Transform outputs is now safe --- src/vs/workbench/api/common/extHostNotebook.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 7a62a3e7ed0..683823daa93 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -345,8 +345,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { let mimeTypes = Object.keys(output.data); - - // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. let coreDisplayOrder = this.renderingHandler.outputDisplayOrder; const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []); @@ -415,7 +413,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo } } -export class NotebookEditorCellEdit { +export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit { private _finalized: boolean = false; private readonly _documentVersionId: number; private _collectedEdits: ICellEditOperation[] = []; @@ -526,13 +524,13 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook })); } - edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable { - const edit = new NotebookEditorCellEdit(this); + edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable { + const edit = new NotebookEditorCellEditBuilder(this); callback(edit); return this._applyEdit(edit); } - private _applyEdit(editBuilder: NotebookEditorCellEdit): Promise { + private _applyEdit(editBuilder: NotebookEditorCellEditBuilder): Promise { const editData = editBuilder.finalize(); // return when there is nothing to do @@ -769,8 +767,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { let mimeTypes = Object.keys(output.data); - - // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. let coreDisplayOrder = this.outputDisplayOrder; const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []); From a9da51fd579a5a42d859dc44ee02c7b42c6e21d2 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 May 2020 18:50:00 -0700 Subject: [PATCH 004/148] document add/delete events --- src/vs/workbench/api/common/extHostNotebook.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 683823daa93..a9ce914cdfe 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -931,6 +931,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } const onDidReceiveMessage = new Emitter(); + const document = this._documents.get(revivedUri.toString())!; let editor = new ExtHostNotebookEditor( viewType, @@ -938,10 +939,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN revivedUri, this._proxy, onDidReceiveMessage, - this._documents.get(revivedUri.toString())!, + document, this._documentsAndEditors ); + this._onDidOpenNotebookDocument.fire(document); + // TODO, does it already exist? this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage }); }); From 6e5f9725ebed511e1777a1bf66ae67e4863889e6 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 6 May 2020 10:44:31 +0200 Subject: [PATCH 005/148] Focus did not change with typing, re-announce element https://github.com/microsoft/vscode/issues/95961 --- src/vs/base/browser/ui/list/listWidget.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 8c9b875936e..3a855c310f2 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -26,6 +26,7 @@ import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice'; import { clamp } from 'vs/base/common/numbers'; import { matchesPrefix } from 'vs/base/common/filters'; import { IDragAndDropData } from 'vs/base/browser/dnd'; +import { alert } from 'vs/base/browser/ui/aria/aria'; interface ITraitChangeEvent { indexes: number[]; @@ -428,6 +429,14 @@ class TypeLabelController implements IDisposable { if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) { this.list.setFocus([index]); this.list.reveal(index); + + if (index === start) { + // Focus did not change with typing, re-announce element https://github.com/microsoft/vscode/issues/95961 + const ariaLabel = this.list.options.accessibilityProvider ? this.list.options.accessibilityProvider.getAriaLabel(this.list.element(index)) : undefined; + if (ariaLabel) { + alert(ariaLabel); + } + } return; } } From 4e80e2fc9b9e73015d9950444610b8bfba287854 Mon Sep 17 00:00:00 2001 From: Isidor Nikolic Date: Wed, 6 May 2020 15:27:44 +0200 Subject: [PATCH 006/148] Update src/vs/base/browser/ui/list/listWidget.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Moreno --- src/vs/base/browser/ui/list/listWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 3a855c310f2..5b59043f4ae 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -432,7 +432,7 @@ class TypeLabelController implements IDisposable { if (index === start) { // Focus did not change with typing, re-announce element https://github.com/microsoft/vscode/issues/95961 - const ariaLabel = this.list.options.accessibilityProvider ? this.list.options.accessibilityProvider.getAriaLabel(this.list.element(index)) : undefined; + const ariaLabel = this.list.options.accessibilityProvider?.getAriaLabel(this.list.element(index)); if (ariaLabel) { alert(ariaLabel); } From b4dd66bc856e0ad1231c5c19e0465412aace0af6 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 11:43:19 -0700 Subject: [PATCH 007/148] revert. --- .../api/browser/mainThreadNotebook.ts | 17 +++++++++-- .../notebook/browser/notebookEditorInput.ts | 12 ++++++++ .../notebook/browser/notebookServiceImpl.ts | 6 ++-- .../browser/viewModel/notebookViewModel.ts | 4 +-- .../common/model/notebookTextModel.ts | 19 +++++++----- .../notebook/common/notebookEditorModel.ts | 30 ++++++++++++++++--- .../notebook/common/notebookService.ts | 4 +-- 7 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 8cafccf0955..a9852215006 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -191,10 +191,23 @@ export class MainThreadNotebookController implements IMainNotebookController { ) { } - async createNotebook(viewType: string, uri: URI, forBackup: boolean): Promise { + async createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise { let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); if (mainthreadNotebook) { + if (forceReload) { + const data = await this._proxy.$resolveNotebookData(viewType, uri); + if (!data) { + return; + } + + mainthreadNotebook.textModel.languages = data.languages; + mainthreadNotebook.textModel.metadata = data.metadata; + mainthreadNotebook.textModel.applyEdit(mainthreadNotebook.textModel.versionId, [ + { editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 }, + { editType: CellEditType.Insert, index: 0, cells: data.cells } + ]); + } return mainthreadNotebook.textModel; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index c84e0cbd255..d0f815bdbf9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -85,6 +85,18 @@ export class NotebookEditorInput extends EditorInput { return undefined; } + async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this; + } + + async revert(group: GroupIdentifier): Promise { + if (this.textModel) { + await this.textModel.revert(); + } + + return; + } + async resolve(): Promise { if (!await this.notebookService.canResolve(this.viewType!)) { throw new Error(`Cannot open notebook of type '${this.viewType}'`); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 30dbe298b03..0f4affb9d95 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -208,7 +208,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return undefined; } - const notebookModel = await provider.controller.createNotebook(viewType, uri, true); + const notebookModel = await provider.controller.createNotebook(viewType, uri, true, false); if (!notebookModel) { return undefined; } @@ -235,7 +235,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return modelData.model; } - async resolveNotebook(viewType: string, uri: URI): Promise { + async resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; @@ -243,7 +243,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu let notebookModel: NotebookTextModel | undefined; - notebookModel = await provider.controller.createNotebook(viewType, uri, false); + notebookModel = await provider.controller.createNotebook(viewType, uri, false, forceReload); // new notebook model created const modelId = MODEL_ID(uri); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index e8016684b27..845b57e6e16 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -586,7 +586,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._viewCells.splice(deleteIndex, 1); this._handleToViewCellMapping.delete(deleteCell.handle); - this._notebook.removeCell(deleteIndex); + this._notebook.removeCell(deleteIndex, 1); this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] }); } @@ -638,7 +638,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._viewCells.splice(index, 1); this._handleToViewCellMapping.delete(viewCell.handle); - this._notebook.removeCell(index); + this._notebook.removeCell(index, 1); let endSelections: number[] = []; if (this.selectionHandles.length) { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index fce799cc033..f768bc5d1ce 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -122,6 +122,9 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } initialize(cells: ICellDto2[]) { + this.cells = []; + this._versionId = 0; + const mainCells = cells.map(cell => { const cellHandle = NotebookTextModel._cellhandlePool++; const cellUri = CellUri.generate(this.uri, cellHandle); @@ -180,7 +183,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this.insertNewCell(insertEdit.index, mainCells); break; case CellEditType.Delete: - this.removeCell(operations[i].index); + this.removeCell(operations[i].index, operations[i].end - operations[i].start); break; } } @@ -307,17 +310,19 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return; } - removeCell(index: number) { + removeCell(index: number, count: number) { this._isUntitled = false; - let cell = this.cells[index]; - this._cellListeners.get(cell.handle)?.dispose(); - this._cellListeners.delete(cell.handle); - this.cells.splice(index, 1); + for (let i = index; i < index + count; i++) { + let cell = this.cells[i]; + this._cellListeners.get(cell.handle)?.dispose(); + this._cellListeners.delete(cell.handle); + } + this.cells.splice(index, count); this._onDidChangeContent.fire(); this._increaseVersionId(); - this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, 1, []]] }); + this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, count, []]] }); } moveCellToIdx(index: number, newIdx: number) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 1ab0ecb3ea5..784c1cdebf9 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -26,6 +26,13 @@ export interface INotebookEditorModelManager { get(resource: URI): NotebookEditorModel | undefined; } +export interface INotebookRevertOptions { + /** + * Go to disk bypassing any cache of the model if any. + */ + forceReadFromDisk?: boolean; +} + export class NotebookEditorModel extends EditorModel implements IWorkingCopy, INotebookEditorModel { private _dirty = false; @@ -63,10 +70,17 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } async revert(options?: IRevertOptions | undefined): Promise { + console.log(options); + await this.load({ forceReadFromDisk: true }); + this._dirty = false; + this._onDidChangeDirty.fire(); return; } - async load(): Promise { + async load(options?: INotebookRevertOptions): Promise { + if (options?.forceReadFromDisk) { + return this.loadFromProvider(true); + } if (this.isResolved()) { return this; } @@ -85,7 +99,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } } - return this.loadFromProvider(); + return this.loadFromProvider(false); } private async loadFromBackup(content: ITextBuffer): Promise { @@ -108,8 +122,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN return this; } - private async loadFromProvider() { - const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource); + private async loadFromProvider(forceReloadFromDisk: boolean) { + const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk); this._notebook = notebook!; this._name = basename(this._notebook!.uri); @@ -144,6 +158,14 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN this._onDidChangeDirty.fire(); return true; } + + async saveAs(targetResource: URI): Promise { + const tokenSource = new CancellationTokenSource(); + await this.notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); + this._dirty = false; + this._onDidChangeDirty.fire(); + return true; + } } export class NotebookEditorModelManager extends Disposable implements INotebookEditorModelManager { diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 2d92cd67e51..e737cbfb348 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -17,7 +17,7 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { - createNotebook(viewType: string, uri: URI, forBackup: boolean): Promise; + createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise; executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; onDidReceiveMessage(uri: URI, message: any): void; executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; @@ -35,7 +35,7 @@ export interface INotebookService { registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void; unregisterNotebookRenderer(handle: number): void; getRendererInfo(handle: number): INotebookRendererInfo | undefined; - resolveNotebook(viewType: string, uri: URI): Promise; + resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise; createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise; executeNotebook(viewType: string, uri: URI): Promise; executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; From 4447915b001ba88c53bc94ddc71b5084e470e1bc Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 12:42:36 -0700 Subject: [PATCH 008/148] saveAs --- .../api/browser/mainThreadNotebook.ts | 5 ++++ .../workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostNotebook.ts | 18 +++++++++-- .../notebook/browser/notebookEditorInput.ts | 30 +++++++++++++++++-- .../notebook/browser/notebookServiceImpl.ts | 10 +++++++ .../notebook/common/notebookEditorModel.ts | 2 +- .../notebook/common/notebookService.ts | 2 ++ 7 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index a9852215006..efdef129e3f 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -309,4 +309,9 @@ export class MainThreadNotebookController implements IMainNotebookController { async save(uri: URI, token: CancellationToken): Promise { return this._proxy.$saveNotebook(this._viewType, uri, token); } + + async saveAs(uri: URI, target: URI, token: CancellationToken): Promise { + return this._proxy.$saveNotebookAs(this._viewType, uri, target, token); + + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4676efcffe5..9340648be72 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1556,6 +1556,7 @@ export interface ExtHostNotebookShape { $resolveNotebookData(viewType: string, uri: UriComponents): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; + $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $onDidReceiveMessage(uri: UriComponents, message: any): void; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index a9ce914cdfe..66dba087614 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -846,10 +846,22 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return true; } - let provider = this._notebookContentProviders.get(viewType); + return false; + } + + async $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise { + let document = this._documents.get(URI.revive(uri).toString()); + if (!document) { + return false; + } + + if (this._notebookContentProviders.has(viewType)) { + try { + await this._notebookContentProviders.get(viewType)!.provider.saveNotebookAs(URI.revive(target), document, token); + } catch (e) { + return false; + } - if (provider && document) { - await provider.provider.saveNotebook(document, token); return true; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index d0f815bdbf9..72cdd118a4c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -6,10 +6,12 @@ import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; -import { isEqual } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class NotebookEditorInput extends EditorInput { @@ -39,7 +41,10 @@ export class NotebookEditorInput extends EditorInput { public name: string, public readonly viewType: string | undefined, @INotebookService private readonly notebookService: INotebookService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IEditorService private readonly editorService: IEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); } @@ -86,7 +91,26 @@ export class NotebookEditorInput extends EditorInput { } async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { - return this; + if (!this.textModel) { + return undefined; + } + + const dialogPath = this.textModel.resource; + const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems); + if (!target) { + return undefined; // save cancelled + } + + if (!await this.textModel.saveAs(target)) { + return undefined; + } + + return this._move(group, target)?.editor; + } + + _move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + const editorInput = NotebookEditorInput.getOrCreate(this.instantiationService, newResource, basename(newResource), this.viewType); + return { editor: editorInput }; } async revert(group: GroupIdentifier): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 0f4affb9d95..ca530378bbc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -324,6 +324,16 @@ export class NotebookService extends Disposable implements INotebookService, ICu return false; } + async saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + return provider.controller.saveAs(resource, target, token); + } + + return false; + } + onDidReceiveMessage(viewType: string, uri: URI, message: any): void { let provider = this._notebookProviders.get(viewType); diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 784c1cdebf9..3660ea57838 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -161,7 +161,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN async saveAs(targetResource: URI): Promise { const tokenSource = new CancellationTokenSource(); - await this.notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); + await this.notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token); this._dirty = false; this._onDidChangeDirty.fire(); return true; diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index e737cbfb348..6a808bafa99 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -23,6 +23,7 @@ export interface IMainNotebookController { executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; removeNotebookDocument(notebook: INotebookTextModel): Promise; save(uri: URI, token: CancellationToken): Promise; + saveAs(uri: URI, target: URI, token: CancellationToken): Promise; } export interface INotebookService { @@ -46,6 +47,7 @@ export interface INotebookService { destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; updateActiveNotebookDocument(viewType: string, resource: URI): void; save(viewType: string, resource: URI, token: CancellationToken): Promise; + saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise; onDidReceiveMessage(viewType: string, uri: URI, message: any): void; setToCopy(items: NotebookCellTextModel[]): void; getToCopy(): NotebookCellTextModel[] | undefined; From e9a1035f5a5254fb09114c0026c12631a9b35cb0 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 13:41:04 -0700 Subject: [PATCH 009/148] :lipstick: --- .../workbench/contrib/notebook/browser/notebookEditorInput.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 72cdd118a4c..50405b6e34c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -11,7 +11,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class NotebookEditorInput extends EditorInput { @@ -43,7 +42,7 @@ export class NotebookEditorInput extends EditorInput { @INotebookService private readonly notebookService: INotebookService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService, + // @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); From 4bd68b7dfe69c72c09d05a880e236d707c9f8b98 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 15:04:52 -0700 Subject: [PATCH 010/148] fix false negative test --- extensions/vscode-notebook-tests/src/notebook.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index e4eba9aefb3..2a85f394229 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -212,7 +212,7 @@ suite('notebook dirty state', () => { await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); - // await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); From e0882b95ff62fb2562264218ac202ccb12bd4753 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 16:42:38 -0700 Subject: [PATCH 011/148] Fix selection after reverting the document --- .../src/notebook.test.ts | 48 +++++++++++++++++++ .../notebook/browser/view/notebookCellList.ts | 18 +++++-- .../browser/viewModel/notebookViewModel.ts | 11 +++-- .../notebook/common/notebookEditorModel.ts | 1 - 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index 2a85f394229..0de2917cf3f 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -272,3 +272,51 @@ suite('notebook undo redo', () => { await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); }); + +suite('notebook working copy', () => { + test('notebook revert on close', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await waitFor(500); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); + + // close active editor from command will revert the file + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await waitFor(500); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[0], vscode.notebook.activeNotebookEditor?.selection); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.source, 'test'); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('notebook revert', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await waitFor(500); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); + await vscode.commands.executeCommand('workbench.action.files.revert'); + + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[0], vscode.notebook.activeNotebookEditor?.selection); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells.length, 1); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.source, 'test'); + + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 5f7766fd2e4..f82bf348e93 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -157,7 +157,7 @@ export class NotebookCellList extends WorkbenchList implements ID for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell)) { + if (this._viewModel!.hasCell(cell.handle)) { hideOutputs.push(...cell?.model.outputs); } else { deletedOutputs.push(...cell?.model.outputs); @@ -177,7 +177,7 @@ export class NotebookCellList extends WorkbenchList implements ID for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell)) { + if (this._viewModel!.hasCell(cell.handle)) { hideOutputs.push(...cell?.model.outputs); } else { deletedOutputs.push(...cell?.model.outputs); @@ -299,7 +299,7 @@ export class NotebookCellList extends WorkbenchList implements ID for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell)) { + if (this._viewModel!.hasCell(cell.handle)) { hideOutputs.push(...cell?.model.outputs); } else { deletedOutputs.push(...cell?.model.outputs); @@ -316,6 +316,18 @@ export class NotebookCellList extends WorkbenchList implements ID splice2(start: number, deleteCount: number, elements: CellViewModel[] = []): void { // we need to convert start and delete count based on hidden ranges super.splice(start, deleteCount, elements); + + const selectionsLeft = []; + this._viewModel!.selectionHandles.forEach(handle => { + if (this._viewModel!.hasCell(handle)) { + selectionsLeft.push(handle); + } + }); + + if (!selectionsLeft.length && this._viewModel!.viewCells) { + // after splice, the selected cells are deleted + this._viewModel!.selectionHandles = [this._viewModel!.viewCells[0].handle]; + } } getViewIndex(cell: ICellViewModel) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 845b57e6e16..5b56bce0939 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -268,7 +268,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD }); diffs.reverse().forEach(diff => { - this._viewCells.splice(diff[0], diff[1], ...diff[2]); + const deletedCells = this._viewCells.splice(diff[0], diff[1], ...diff[2]); + + deletedCells.forEach(cell => { + this._handleToViewCellMapping.delete(cell.handle); + }); + diff[2].forEach(cell => { this._handleToViewCellMapping.set(cell.handle, cell); this._localStore.add(cell); @@ -456,8 +461,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return index + 1; } - hasCell(cell: ICellViewModel) { - return this._handleToViewCellMapping.has(cell.handle); + hasCell(handle: number) { + return this._handleToViewCellMapping.has(handle); } getVersionId() { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 3660ea57838..bad8f0edc22 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -70,7 +70,6 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } async revert(options?: IRevertOptions | undefined): Promise { - console.log(options); await this.load({ forceReadFromDisk: true }); this._dirty = false; this._onDidChangeDirty.fire(); From 9836cd2f6850610e4c9c56493a1f9af89c820408 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 16:53:28 -0700 Subject: [PATCH 012/148] Fix #97108 --- src/vs/workbench/contrib/notebook/browser/media/notebook.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 1a7895cefd8..e17833c1ff5 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -21,6 +21,10 @@ overflow: visible !important; } */ +.monaco-workbench .part.editor > .content .notebook-editor .simple-fr-find-part-wrapper.visible { + z-index: 100; +} + .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .overflowingContentWidgets > div { z-index: 600 !important; /* @rebornix: larger than the editor title bar */ From 28b887934d895847c88677388fa891b2e58a7935 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 17:30:58 -0700 Subject: [PATCH 013/148] Fix #96685 --- .../notebook/browser/notebookEditorInput.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 50405b6e34c..b392a0fab2d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; -import { isEqual, basename } from 'vs/base/common/resources'; +import { isEqual, basename, extname } from 'vs/base/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; @@ -107,6 +107,17 @@ export class NotebookEditorInput extends EditorInput { return this._move(group, target)?.editor; } + move(group: GroupIdentifier, target: URI): IMoveResult | undefined { + if (this.textModel) { + const contributedNotebookProviders = this.notebookService.getContributedNotebookProviders(target); + + if (contributedNotebookProviders.find(provider => provider.id === this.textModel!.viewType)) { + return this._move(group, target); + } + } + return undefined; + } + _move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { const editorInput = NotebookEditorInput.getOrCreate(this.instantiationService, newResource, basename(newResource), this.viewType); return { editor: editorInput }; From b38ccc0084ce6f6ad289e3a8866db023d38001d8 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 17:48:21 -0700 Subject: [PATCH 014/148] remove unused. --- .../workbench/contrib/notebook/browser/notebookEditorInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index b392a0fab2d..f923d14f0b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -6,7 +6,7 @@ import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; -import { isEqual, basename, extname } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; From 73bf9d35da698ed6438195e4ebec1ac32ec28180 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 May 2020 10:20:06 +0200 Subject: [PATCH 015/148] list: announce once element typing is done --- src/vs/base/browser/ui/list/listWidget.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 5b59043f4ae..0f4fab08c0f 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -394,6 +394,7 @@ class TypeLabelController implements IDisposable { const onInput = Event.reduce(Event.any(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i)); onInput(this.onInput, this, this.enabledDisposables); + onClear(this.onClear, this, this.enabledDisposables); this.enabled = true; this.triggered = false; @@ -409,6 +410,16 @@ class TypeLabelController implements IDisposable { this.triggered = false; } + private onClear(): void { + const focus = this.list.getFocus(); + if (focus.length > 0) { + const ariaLabel = this.list.options.accessibilityProvider?.getAriaLabel(this.list.element(focus[0])); + if (ariaLabel) { + alert(ariaLabel); + } + } + } + private onInput(word: string | null): void { if (!word) { this.state = TypeLabelControllerState.Idle; @@ -429,14 +440,6 @@ class TypeLabelController implements IDisposable { if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) { this.list.setFocus([index]); this.list.reveal(index); - - if (index === start) { - // Focus did not change with typing, re-announce element https://github.com/microsoft/vscode/issues/95961 - const ariaLabel = this.list.options.accessibilityProvider?.getAriaLabel(this.list.element(index)); - if (ariaLabel) { - alert(ariaLabel); - } - } return; } } From ee4ccef5b2f692ea9ca92093005b7bafe513f107 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 May 2020 10:39:27 +0200 Subject: [PATCH 016/148] do not announce if only one character typed to avoid duplicate announcment --- src/vs/base/browser/ui/list/listWidget.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 0f4fab08c0f..c9286726313 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -345,6 +345,7 @@ class TypeLabelController implements IDisposable { private automaticKeyboardNavigation = true; private triggered = false; + private charactersTyped = 0; private readonly enabledDisposables = new DisposableStore(); private readonly disposables = new DisposableStore(); @@ -412,12 +413,15 @@ class TypeLabelController implements IDisposable { private onClear(): void { const focus = this.list.getFocus(); - if (focus.length > 0) { + if (focus.length > 0 && this.charactersTyped > 1) { + // List: re-anounce element on typing end since typed keys will interupt aria label of focused element + // Do not announce if only one character typed to avoid duplicate announcment https://github.com/microsoft/vscode/issues/95961 const ariaLabel = this.list.options.accessibilityProvider?.getAriaLabel(this.list.element(focus[0])); if (ariaLabel) { alert(ariaLabel); } } + this.charactersTyped = 0; } private onInput(word: string | null): void { @@ -427,6 +431,7 @@ class TypeLabelController implements IDisposable { return; } + this.charactersTyped++; const focus = this.list.getFocus(); const start = focus.length > 0 ? focus[0] : 0; const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0; From 01a7dfc3894ee3d768498d67d344a0878ec2f1b7 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 7 May 2020 11:50:10 +0200 Subject: [PATCH 017/148] list: store previously focused on typeing naviation --- src/vs/base/browser/ui/list/listWidget.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index c9286726313..b271eea461b 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -345,7 +345,7 @@ class TypeLabelController implements IDisposable { private automaticKeyboardNavigation = true; private triggered = false; - private charactersTyped = 0; + private previouslyFocused = -1; private readonly enabledDisposables = new DisposableStore(); private readonly disposables = new DisposableStore(); @@ -413,15 +413,15 @@ class TypeLabelController implements IDisposable { private onClear(): void { const focus = this.list.getFocus(); - if (focus.length > 0 && this.charactersTyped > 1) { + if (focus.length > 0 && focus[0] === this.previouslyFocused) { // List: re-anounce element on typing end since typed keys will interupt aria label of focused element - // Do not announce if only one character typed to avoid duplicate announcment https://github.com/microsoft/vscode/issues/95961 + // Do not announce if there was a focus change at the end to prevent duplication https://github.com/microsoft/vscode/issues/95961 const ariaLabel = this.list.options.accessibilityProvider?.getAriaLabel(this.list.element(focus[0])); if (ariaLabel) { alert(ariaLabel); } } - this.charactersTyped = 0; + this.previouslyFocused = -1; } private onInput(word: string | null): void { @@ -431,7 +431,6 @@ class TypeLabelController implements IDisposable { return; } - this.charactersTyped++; const focus = this.list.getFocus(); const start = focus.length > 0 ? focus[0] : 0; const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0; @@ -443,6 +442,7 @@ class TypeLabelController implements IDisposable { const labelStr = label && label.toString(); if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) { + this.previouslyFocused = start; this.list.setFocus([index]); this.list.reveal(index); return; From ddbaab0fbc1d0c48a2ef7e855a56d21b2f63dc66 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 7 May 2020 09:38:09 -0700 Subject: [PATCH 018/148] Re #96564 --- .../contrib/notebook/browser/notebookEditorInput.ts | 6 +++--- .../contrib/notebook/common/notebookEditorModel.ts | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index f923d14f0b1..fd7dff392af 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult, IRevertOptions } from 'vs/workbench/common/editor'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; import { isEqual, basename } from 'vs/base/common/resources'; @@ -123,9 +123,9 @@ export class NotebookEditorInput extends EditorInput { return { editor: editorInput }; } - async revert(group: GroupIdentifier): Promise { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { if (this.textModel) { - await this.textModel.revert(); + await this.textModel.revert(options); } return; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index bad8f0edc22..823c790efbb 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -70,6 +70,11 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } async revert(options?: IRevertOptions | undefined): Promise { + if (options?.soft) { + await this.backupFileService.discardBackup(this.resource); + return; + } + await this.load({ forceReadFromDisk: true }); this._dirty = false; this._onDidChangeDirty.fire(); From 6fe68539e5261a28d6a0ebf65549c54f29c84a4b Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 7 May 2020 12:42:12 -0700 Subject: [PATCH 019/148] Fix #97176 add bottom padding to search message --- src/vs/workbench/contrib/search/browser/media/searchview.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index 27ad06b314c..8c8cedc0472 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -152,9 +152,7 @@ } .search-view .message { - padding-left: 22px; - padding-right: 22px; - padding-top: 0px; + padding: 0 22px 8px; } .search-view .message p:first-child { From f005bafd38c8814a532959538d327aa5abb611a7 Mon Sep 17 00:00:00 2001 From: Christopher Maynard Date: Thu, 7 May 2020 16:59:58 -0400 Subject: [PATCH 020/148] Allows focus to be moved inside notebook output. --- .../notebook/browser/contrib/coreActions.ts | 48 ++++++-- .../notebook/browser/notebookBrowser.ts | 6 +- .../notebook/browser/notebookEditor.ts | 34 ++++-- .../view/renderers/backLayerWebView.ts | 113 ++++++++++++++++-- .../notebook/test/testNotebookEditor.ts | 28 ++--- 5 files changed, 182 insertions(+), 47 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index b4d5ce34e00..421b262507f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -4,24 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { 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'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; import { localize } from 'vs/nls'; import { Action2, IAction2Options, MenuId, MenuItemAction, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { BaseCellRenderTemplate, CellEditState, CellRunState, ICellViewModel, INotebookEditor, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_EDITABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { URI } from 'vs/base/common/uri'; +import { BaseCellRenderTemplate, CellEditState, CellRunState, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } 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'; // Notebook Commands const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; @@ -66,6 +66,7 @@ const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; const CHANGE_CELL_LANGUAGE = 'notebook.cell.changeLanguage'; +const FOCUS_OUTPUT_COMMAND_ID = "notebook.cell.focusOutput"; export const NOTEBOOK_ACTIONS_CATEGORY = localize('notebookActions.category', "Notebook"); @@ -1093,6 +1094,35 @@ registerAction2(class extends Action2 { } }); +registerAction2(class extends Action2 { + constructor() { + super({ + id: FOCUS_OUTPUT_COMMAND_ID, + title: localize('focusOutput', 'Focus output'), + category: NOTEBOOK_ACTIONS_CATEGORY, + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + weight: EDITOR_WIDGET_ACTION_WEIGHT + } + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(context)) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + const editor = context.notebookEditor; + const activeCell = context.cell; + editor.focusNotebookCell(activeCell, false, true); + } +}); + + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 8fd1f41dfc6..c96fc4299d1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -15,10 +15,10 @@ import { ScrollEvent } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { Range } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IReadonlyTextBuffer, ITextModel } from 'vs/editor/common/model'; +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { CellLanguageStatusBarItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; @@ -224,7 +224,7 @@ export interface INotebookEditor { /** * Focus the container of a cell (the monaco editor inside is not focused). */ - focusNotebookCell(cell: ICellViewModel, focusEditor: boolean): void; + focusNotebookCell(cell: ICellViewModel, focusEditor: boolean, focusOutput?: boolean): void; /** * Execute the given notebook cell diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 007ed398466..b4158f27e2e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -3,17 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/notebook'; import { getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, MutableDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/notebook'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; import * as nls from 'vs/nls'; @@ -28,25 +30,23 @@ import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/com import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorOptions, IEditorCloseEvent, IEditorMemento } from 'vs/workbench/common/editor'; -import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, INotebookEditorContribution, NOTEBOOK_EDITOR_RUNNABLE, IEditableCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, IEditableCellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; -import { CodeCellRenderer, MarkdownCellRenderer, NotebookCellListDelegate, CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; +import { CellDragAndDropController, CodeCellRenderer, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind, CellUri, IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IPosition, Position } from 'vs/editor/common/core/position'; const $ = DOM.$; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -1116,7 +1116,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } } - focusNotebookCell(cell: ICellViewModel, focusEditor: boolean) { + focusNotebookCell(cell: ICellViewModel, focusEditor: boolean, focusOuput?: boolean) { if (focusEditor) { this.selectElement(cell); this.list?.focusView(); @@ -1124,6 +1124,18 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { cell.editState = CellEditState.Editing; cell.focusMode = CellFocusMode.Editor; this.revealInCenterIfOutsideViewport(cell); + } else if (focusOuput) { + this.selectElement(cell); + this.list?.focusView(); + + if (!this.webview) { + return; + } + this.webview.focusOutput(cell.id); + + cell.editState = CellEditState.Preview; + cell.focusMode = CellFocusMode.Container; + this.revealInCenterIfOutsideViewport(cell); } else { let itemDOM = this.list?.domElementOfElement(cell); if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 9cd53cc37ff..15c5dc6e5d8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -4,22 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import * as path from 'vs/base/common/path'; +import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { CELL_MARGIN, CELL_RUN_GUTTER } from 'vs/workbench/contrib/notebook/browser/constants'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader'; -import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { CELL_MARGIN, CELL_RUN_GUTTER } from 'vs/workbench/contrib/notebook/browser/constants'; -import { Emitter, Event } from 'vs/base/common/event'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { isWeb } from 'vs/base/common/platform'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export interface IDimensionMessage { __vscode_notebook_message: boolean; @@ -54,10 +54,22 @@ export interface IScrollAckMessage { version: number; } +export interface IBlurOutputMessage { + __vscode_notebook_message: boolean; + type: 'focus-editor'; + id: string; + focusNext?: boolean; +} + export interface IClearMessage { type: 'clear'; } +export interface IFocusOutputMessage { + type: 'focus-output'; + id: string; +} + export interface ICreationRequestMessage { type: 'html'; content: string; @@ -93,7 +105,7 @@ export interface IUpdatePreloadResourceMessage { resources: string[]; } -type IMessage = IDimensionMessage | IScrollAckMessage | IWheelMessage | IMouseEnterMessage | IMouseLeaveMessage; +type IMessage = IDimensionMessage | IScrollAckMessage | IWheelMessage | IMouseEnterMessage | IMouseLeaveMessage | IBlurOutputMessage; let version = 0; export class BackLayerWebView extends Disposable { @@ -108,6 +120,7 @@ export class BackLayerWebView extends Disposable { private readonly _onMessage = this._register(new Emitter()); public readonly onMessage: Event = this._onMessage.event; private _initalized: Promise; + private activeCellId: string | undefined; constructor( @@ -288,10 +301,23 @@ ${loaderJs} let cellOutputContainer = document.getElementById(id); let outputId = event.data.outputId; if (!cellOutputContainer) { + const container = document.getElementById('container'); + + let upperWrapperElement = document.createElement('div'); + upperWrapperElement.tabIndex = 0; + container.appendChild(upperWrapperElement); + upperWrapperElement.addEventListener('focus', () => { + vscode.postMessage({ + __vscode_notebook_message: true, + type: 'focus-editor', + id: outputId, + }); + }); + let newElement = document.createElement('div'); newElement.id = id; - document.getElementById('container').appendChild(newElement); + container.appendChild(newElement); cellOutputContainer = newElement; cellOutputContainer.addEventListener('mouseenter', () => { @@ -310,6 +336,32 @@ ${loaderJs} data: { } }); }); + + const handleKeyDown = (event) => { + if (event.defaultPrevented || !(event.key === 'ArrowUp' && event.ctrlKey)) { + return; + } + + vscode.postMessage({ + __vscode_notebook_message: true, + type: 'focus-editor', + id: outputId, + }); + }; + + cellOutputContainer.addEventListener("keydown", handleKeyDown); + + let lowerWrapperElement = document.createElement('div'); + lowerWrapperElement.tabIndex = 0; + container.appendChild(lowerWrapperElement); + lowerWrapperElement.addEventListener('focus', () => { + vscode.postMessage({ + __vscode_notebook_message: true, + type: 'focus-editor', + id: outputId, + focusNext: true + }); + }); } let outputNode = document.createElement('div'); @@ -384,6 +436,15 @@ ${loaderJs} preloadsContainer.appendChild(scriptTag) } break; + case 'focus-output': + { + let cellOutputContainer = document.getElementById(id); + if(cellOutputContainer){ + const focusableElement = cellOutputContainer.querySelector('[tabindex="0"], [href], button, input, option, select, textarea'); + focusableElement && focusableElement.focus(); + } + break; + } } }); }()); @@ -405,6 +466,14 @@ ${loaderJs} initialize(content: string) { this.webview = this._createInset(this.webviewService, content); this.webview.mountTo(this.element); + this.webview.onDidFocus(() => { + if (this.activeCellId) { + this.webview.sendMessage({ + type: 'focus-output', + id: this.activeCellId + }); + } + }); this._register(this.webview.onDidClickLink(link => { this.openerService.open(link, { fromUserGesture: true }); @@ -445,6 +514,25 @@ ${loaderJs} preventDefault: () => { }, stopPropagation: () => { } }); + } else if (data.type === 'focus-editor') { + const info = this.resolveOutputId(data.id); + if (info) { + if (data.focusNext) { + const idx = this.notebookEditor.viewModel?.getCellIndex(info.cell); + if (typeof idx !== 'number') { + return; + } + + const newCell = this.notebookEditor.viewModel?.viewCells[idx + 1]; + if (!newCell) { + return; + } + + this.notebookEditor.focusNotebookCell(newCell, true); + } else { + this.notebookEditor.focusNotebookCell(info.cell, true); + } + } } return; } @@ -589,6 +677,11 @@ ${loaderJs} this.reversedInsetMapping = new Map(); } + focusOutput(cellId: string) { + this.activeCellId = cellId; + this.webview.focus(); + } + updateRendererPreloads(preloads: Set) { let resources: string[] = []; let extensionLocations: URI[] = []; diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 35ded8ed17d..ec36e6928c8 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -3,24 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; -import { CellKind, IOutput, CellUri, NotebookCellMetadata, INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookViewModel, IModelDecorationsChangeAccessor, CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { INotebookEditor, NotebookLayoutInfo, ICellViewModel, ICellRange, INotebookEditorMouseEvent, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { Range } from 'vs/editor/common/core/range'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; -import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter, Event } from 'vs/base/common/event'; import { EditorModel } from 'vs/workbench/common/editor'; +import { ICellRange, ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; +import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CellKind, CellUri, INotebookEditorModel, IOutput, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; export class TestCell extends NotebookCellTextModel { constructor( public viewType: string, @@ -171,7 +171,7 @@ export class TestNotebookEditor implements INotebookEditor { saveNotebookCell(cell: CellViewModel): void { // throw new Error('Method not implemented.'); } - focusNotebookCell(cell: CellViewModel, focusEditor: boolean): void { + focusNotebookCell(cell: CellViewModel, focusEditor: boolean, focusOutput?: boolean): void { // throw new Error('Method not implemented.'); } getActiveCell(): CellViewModel | undefined { From 0e24350fa7722252c1996810b19664676959e50e Mon Sep 17 00:00:00 2001 From: Christopher Maynard Date: Thu, 7 May 2020 17:31:17 -0400 Subject: [PATCH 021/148] Spacing --- src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 421b262507f..98887e4f849 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -690,7 +690,6 @@ registerAction2(class extends Action2 { } }); - registerAction2(class extends Action2 { constructor() { super( From f50f16e92fd978efda2d98103bc7b50754c312dd Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 7 May 2020 14:39:14 -0700 Subject: [PATCH 022/148] Fix #97180 --- .../contrib/searchEditor/browser/searchEditorSerialization.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index 2557745c9d1..eccc82f3e84 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -114,7 +114,7 @@ const contentPatternToSearchConfiguration = (pattern: ITextQuery, includes: stri wholeWord: !!pattern.contentPattern.isWordMatch, excludes, includes, showIncludesExcludes: !!(includes || excludes || pattern?.userDisabledExcludesAndIgnoreFiles), - useIgnores: !!(pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? undefined : !pattern.userDisabledExcludesAndIgnoreFiles), + useIgnores: (pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? true : !pattern.userDisabledExcludesAndIgnoreFiles), contextLines, }; }; From ab7b7f42c43451e60f908ff3c4a52f1d888735cb Mon Sep 17 00:00:00 2001 From: Christopher Maynard Date: Thu, 7 May 2020 18:10:52 -0400 Subject: [PATCH 023/148] Changed double quotes to single --- .../workbench/contrib/notebook/browser/contrib/coreActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 98887e4f849..124f526690e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -66,7 +66,7 @@ const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; const CHANGE_CELL_LANGUAGE = 'notebook.cell.changeLanguage'; -const FOCUS_OUTPUT_COMMAND_ID = "notebook.cell.focusOutput"; +const FOCUS_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutput'; export const NOTEBOOK_ACTIONS_CATEGORY = localize('notebookActions.category', "Notebook"); From c665ac5e3f360f8c9374c033be846117284d4123 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 7 May 2020 15:21:22 -0700 Subject: [PATCH 024/148] avoid spliting text buffer to lines --- .../notebook/browser/notebookBrowser.ts | 1 - .../notebook/browser/notebookEditor.ts | 19 +++++++++---------- .../browser/view/renderers/markdownCell.ts | 1 - .../browser/viewModel/baseCellViewModel.ts | 19 ------------------- 4 files changed, 9 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 8fd1f41dfc6..944bb4d39fb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -110,7 +110,6 @@ export interface ICellViewModel { resolveTextModel(): Promise; getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata; getSelectionsStartPosition(): IPosition[] | undefined; - getLinesContent(): string[]; } export interface IEditableCellViewModel extends ICellViewModel { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 978435b1151..02af1db80c4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -47,6 +47,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IPosition, Position } from 'vs/editor/common/core/position'; +import { IReadonlyTextBuffer } from 'vs/editor/common/model'; const $ = DOM.$; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -781,11 +782,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { return newCell; } - private isAtEOL(p: IPosition, lines: string[]) { - const line = lines[p.lineNumber - 1]; - return line.length + 1 === p.column; - } - private pushIfAbsent(positions: IPosition[], p: IPosition) { const last = positions.length > 0 ? positions[positions.length - 1] : undefined; if (!last || last.lineNumber !== p.lineNumber || last.column !== p.column) { @@ -798,8 +794,12 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { * Move end of line split points to the beginning of the next line; * Avoid duplicate split points */ - private splitPointsToBoundaries(splitPoints: IPosition[], lines: string[]): IPosition[] | null { + private splitPointsToBoundaries(splitPoints: IPosition[], textBuffer: IReadonlyTextBuffer): IPosition[] | null { const boundaries: IPosition[] = []; + const lineCnt = textBuffer.getLineCount(); + const getLineLen = (lineNumber: number) => { + return textBuffer.getLineLength(lineNumber); + }; // split points need to be sorted splitPoints = splitPoints.sort((l, r) => { @@ -812,22 +812,21 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.pushIfAbsent(boundaries, new Position(1, 1)); for (let sp of splitPoints) { - if (this.isAtEOL(sp, lines) && sp.lineNumber < lines.length) { + if (getLineLen(sp.lineNumber) + 1 === sp.column && sp.lineNumber < lineCnt) { sp = new Position(sp.lineNumber + 1, 1); } this.pushIfAbsent(boundaries, sp); } // eat-up any split point at the beginning, i.e. we ignore the split point at the very end - this.pushIfAbsent(boundaries, new Position(lines.length, lines[lines.length - 1].length + 1)); + this.pushIfAbsent(boundaries, new Position(lineCnt, getLineLen(lineCnt) + 1)); // if we only have two then they describe the whole range and nothing needs to be split return boundaries.length > 2 ? boundaries : null; } private computeCellLinesContents(cell: IEditableCellViewModel, splitPoints: IPosition[]): string[] | null { - const lines = cell.getLinesContent(); - const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, lines); + const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, cell.textBuffer); if (!rangeBoundaries) { return null; } 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 7be807d4ac7..8d276c0a999 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -250,7 +250,6 @@ export class StatefullMarkdownCell extends Disposable { bindEditorListeners(model: ITextModel, dimension?: IDimension) { this.localDisposables.add(model.onDidChangeContent(() => { // we don't need to update view cell text anymore as the textbuffer is shared - // this.viewCell.setText(model.getLinesContent()); this.viewCell.clearHTML(); let clientHeight = this.markdownContainer.clientHeight; this.markdownContainer.innerHTML = ''; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 8702fb0ea9b..e17d301efb3 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -205,25 +205,6 @@ export abstract class BaseCellViewModel extends Disposable { return this.model.getValue(); } - getLinesContent(): string[] { - if (this._textModel) { - return this._textModel.getLinesContent(); - } - - return this.model.textBuffer.getLinesContent(); - } - - // setLinesContent(value: string[]) { - // if (this._textModel) { - // // TODO @rebornix we should avoid creating a new string here - // return this._textModel.setValue(value.join('\n')); - // } else { - // const range = this.model.getFullModelRange(); - // this.model.textBuffer. - // this.model.source = value; - // } - // } - private saveViewState(): void { if (!this._textEditor) { return; From b970464a9bd45a95534c0ccd03f0f737e8bf547c Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 7 May 2020 15:55:56 -0700 Subject: [PATCH 025/148] Formatting --- .../workbench/contrib/searchEditor/browser/searchEditorInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 1faf608f211..cae18d4f411 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -36,7 +36,7 @@ import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapab export type SearchConfiguration = { query: string, includes: string, - excludes: string + excludes: string, contextLines: number, wholeWord: boolean, caseSensitive: boolean, From 1797c84949ec96134196b5aeea57fa5b1c4b6ded Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 7 May 2020 16:00:26 -0700 Subject: [PATCH 026/148] Allow passing args to search editor. Closees #95582. --- .../contrib/searchEditor/browser/constants.ts | 2 + .../browser/searchEditor.contribution.ts | 66 ++++++++++--------- .../browser/searchEditorActions.ts | 25 +++++-- 3 files changed, 58 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts index 0040e022187..cc24ea515f6 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -12,3 +12,5 @@ export const SearchEditorScheme = 'search-editor'; export const SearchEditorFindMatchClass = 'seaarchEditorFindMatch'; export const SearchEditorID = 'workbench.editor.searchEditor'; + +export const OpenNewEditorCommandId = 'search.action.openNewEditor'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 223ec269470..c56b06cec02 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -8,9 +8,10 @@ import * as objects from 'vs/base/common/objects'; import { endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { Range } from 'vs/editor/common/core/range'; import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; import { localize } from 'vs/nls'; -import { MenuId, SyncActionDescriptor, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -20,20 +21,18 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry, ActiveEditorContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { IViewsService } from 'vs/workbench/common/views'; +import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; +import { searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { modifySearchEditorContextLinesCommand, OpenSearchEditorAction, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, createEditorFromSearchResult, openNewSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; -import { getOrMakeSearchEditorInput, SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { createEditorFromSearchResult, modifySearchEditorContextLinesCommand, openNewSearchEditor, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { getOrMakeSearchEditorInput, SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { parseSavedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; -import { Range } from 'vs/editor/common/core/range'; -import { searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; -import { IViewsService } from 'vs/workbench/common/views'; -import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const OpenInEditorCommandId = 'search.action.openInEditor'; @@ -210,13 +209,35 @@ CommandsRegistry.registerCommand( //#endregion //#region Actions -const registry = Registry.as(ActionExtensions.WorkbenchActions); const category = localize('search', "Search Editor"); -// TODO: Not an action2 becuase used in view pane container action bar, which uses actions -registry.registerWorkbenchAction( - SyncActionDescriptor.from(OpenSearchEditorAction), - 'Search Editor: Open New Search Editor', category); +registerAction2(class extends Action2 { + constructor() { + super({ + id: SearchEditorConstants.OpenNewEditorCommandId, + title: localize('search.openNewSearchEditor', "Open new Search Editor"), + category, + f1: true, + }); + } + async run(accessor: ServicesAccessor, args: Partial) { + await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: OpenNewEditorToSideCommandId, + title: localize('search.openNewEditorToSide', "Open new Search Editor to the Side"), + category, + f1: true, + }); + } + async run(accessor: ServicesAccessor, args: Partial) { + await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args, true); + } +}); registerAction2(class extends Action2 { constructor() { @@ -242,21 +263,6 @@ registerAction2(class extends Action2 { } }); -registerAction2(class extends Action2 { - constructor() { - super({ - id: OpenNewEditorToSideCommandId, - title: localize('search.openNewEditorToSide', "Open New Search Editor to Side"), - category, - f1: true, - }); - } - async run(accessor: ServicesAccessor) { - const instantiationService = accessor.get(IInstantiationService); - await instantiationService.invokeFunction(openNewSearchEditor, true); - } -}); - registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index acf2e2672ac..19acdf21da1 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -14,13 +14,17 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { getOrMakeSearchEditorInput, SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { searchNewEditorIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; - -const OpenNewEditorCommandId = 'search.action.openNewEditor'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { Schemas } from 'vs/base/common/network'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { OpenNewEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); @@ -95,12 +99,23 @@ export class OpenSearchEditorAction extends Action { } export const openNewSearchEditor = - async (accessor: ServicesAccessor, toSide = false) => { + async (accessor: ServicesAccessor, args: Partial = {}, toSide = false) => { const editorService = accessor.get(IEditorService); const telemetryService = accessor.get(ITelemetryService); const instantiationService = accessor.get(IInstantiationService); const configurationService = accessor.get(IConfigurationService); + const configurationResolverService = accessor.get(IConfigurationResolverService); + const workspaceContextService = accessor.get(IWorkspaceContextService); + const historyService = accessor.get(IHistoryService); + const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); + const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + + const resolvedArgs: Record = {}; + Object.entries(args).forEach(([name, value]) => { + resolvedArgs[name as any] = (typeof value === 'string') ? configurationResolverService.resolve(lastActiveWorkspaceRoot, value) : value; + }); + const activeEditorControl = editorService.activeTextEditorControl; let activeModel: ICodeEditor | undefined; let selected = ''; @@ -125,7 +140,7 @@ export const openNewSearchEditor = telemetryService.publicLog2('searchEditor/openNewSearchEditor'); - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected }, text: '' }); + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected, ...resolvedArgs }, text: '' }); const editor = await editorService.openEditor(input, { pinned: true }, toSide ? SIDE_GROUP : ACTIVE_GROUP) as SearchEditor; if (selected && configurationService.getValue('search').searchOnType) { From 08e579141d20e8b9c40851a9853a5e29849121bb Mon Sep 17 00:00:00 2001 From: Christopher Maynard Date: Thu, 7 May 2020 19:07:58 -0400 Subject: [PATCH 027/148] Responded to PR feedback --- .../notebook/browser/contrib/coreActions.ts | 38 +++++++++---------- .../notebook/browser/notebookBrowser.ts | 2 +- .../notebook/browser/notebookEditor.ts | 6 +-- .../view/renderers/backLayerWebView.ts | 5 ++- .../browser/view/renderers/cellRenderer.ts | 10 ++--- .../notebook/test/testNotebookEditor.ts | 2 +- 6 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 124f526690e..47dc9283ea8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -208,11 +208,11 @@ registerAction2(class extends Action2 { // Try to select below, fall back on inserting const nextCell = editor.viewModel?.viewCells[idx + 1]; if (nextCell) { - editor.focusNotebookCell(nextCell, activeCell.editState === CellEditState.Editing); + editor.focusNotebookCell(nextCell, activeCell.editState === CellEditState.Editing ? 'editor' : 'container'); } else { const newCell = editor.insertNotebookCell(activeCell, CellKind.Code, 'below'); if (newCell) { - editor.focusNotebookCell(newCell, true); + editor.focusNotebookCell(newCell, 'editor'); } } } @@ -246,7 +246,7 @@ registerAction2(class extends Action2 { const newCell = editor.insertNotebookCell(activeCell, CellKind.Code, 'below'); if (newCell) { - editor.focusNotebookCell(newCell, true); + editor.focusNotebookCell(newCell, 'editor'); } } }); @@ -321,7 +321,7 @@ registerAction2(class extends Action2 { activeCell.editState = CellEditState.Preview; } - editor.focusNotebookCell(activeCell, false); + editor.focusNotebookCell(activeCell, 'container'); } } }); @@ -474,7 +474,7 @@ export async function changeCellToKind(kind: CellKind, context: INotebookCellAct newCell.model.language = language; } - notebookEditor.focusNotebookCell(newCell, cell.editState === CellEditState.Editing); + notebookEditor.focusNotebookCell(newCell, cell.editState === CellEditState.Editing ? 'editor' : 'container'); notebookEditor.deleteNotebookCell(cell); return newCell; @@ -529,7 +529,7 @@ abstract class InsertCellCommand extends Action2 { const newCell = context.notebookEditor.insertNotebookCell(context.cell, this.kind, this.direction, undefined, context.ui); if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, true); + context.notebookEditor.focusNotebookCell(newCell, 'editor'); } } } @@ -730,12 +730,12 @@ registerAction2(class extends Action2 { // deletion succeeds, move focus to the next cell const nextCellIdx = index < context.notebookEditor.viewModel!.length ? index : context.notebookEditor.viewModel!.length - 1; if (nextCellIdx >= 0) { - context.notebookEditor.focusNotebookCell(context.notebookEditor.viewModel!.viewCells[nextCellIdx], false); + context.notebookEditor.focusNotebookCell(context.notebookEditor.viewModel!.viewCells[nextCellIdx], 'container'); } else { // No cells left, insert a new empty one const newCell = context.notebookEditor.insertNotebookCell(undefined, context.cell.cellKind); if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, true); + context.notebookEditor.focusNotebookCell(newCell, 'editor'); } } } @@ -749,7 +749,7 @@ async function moveCell(context: INotebookCellActionContext, direction: 'up' | ' if (result) { // move cell command only works when the cell container has focus - context.notebookEditor.focusNotebookCell(context.cell, false); + context.notebookEditor.focusNotebookCell(context.cell, 'container'); } } @@ -758,7 +758,7 @@ async function copyCell(context: INotebookCellActionContext, direction: 'up' | ' const newCellDirection = direction === 'up' ? 'above' : 'below'; const newCell = context.notebookEditor.insertNotebookCell(context.cell, context.cell.cellKind, newCellDirection, text); if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, false); + context.notebookEditor.focusNotebookCell(newCell, 'container'); } } @@ -1044,7 +1044,7 @@ registerAction2(class extends Action2 { return; } - editor.focusNotebookCell(newCell, true); + editor.focusNotebookCell(newCell, 'editor'); } }); @@ -1089,7 +1089,7 @@ registerAction2(class extends Action2 { return; } - editor.focusNotebookCell(newCell, true); + editor.focusNotebookCell(newCell, 'editor'); } }); @@ -1100,7 +1100,7 @@ registerAction2(class extends Action2 { title: localize('focusOutput', 'Focus output'), category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, weight: EDITOR_WIDGET_ACTION_WEIGHT } @@ -1117,7 +1117,7 @@ registerAction2(class extends Action2 { const editor = context.notebookEditor; const activeCell = context.cell; - editor.focusNotebookCell(activeCell, false, true); + editor.focusNotebookCell(activeCell, 'output'); } }); @@ -1216,7 +1216,7 @@ registerAction2(class extends Action2 { } const firstCell = editor.viewModel.viewCells[0]; - editor.focusNotebookCell(firstCell, false); + editor.focusNotebookCell(firstCell, 'container'); } }); @@ -1250,7 +1250,7 @@ registerAction2(class extends Action2 { } const firstCell = editor.viewModel.viewCells[editor.viewModel.length - 1]; - editor.focusNotebookCell(firstCell, false); + editor.focusNotebookCell(firstCell, 'container'); } }); @@ -1365,7 +1365,7 @@ export class ChangeCellLanguageAction extends Action2 { if (selection.languageId === 'markdown' && context.cell?.language !== 'markdown') { const newCell = await changeCellToKind(CellKind.Markdown, { cell: context.cell, notebookEditor: context.notebookEditor }); if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, true); + context.notebookEditor.focusNotebookCell(newCell, 'editor'); } } else if (selection.languageId !== 'markdown' && context.cell?.language === 'markdown') { await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor }, selection.languageId); @@ -1434,7 +1434,7 @@ async function splitCell(context: INotebookCellActionContext): Promise { if (context.cell.cellKind === CellKind.Code) { const newCells = await context.notebookEditor.splitNotebookCell(context.cell); if (newCells) { - context.notebookEditor.focusNotebookCell(newCells[newCells.length - 1], true); + context.notebookEditor.focusNotebookCell(newCells[newCells.length - 1], 'editor'); } } } @@ -1472,7 +1472,7 @@ registerAction2(class extends Action2 { async function joinCells(context: INotebookCellActionContext, direction: 'above' | 'below'): Promise { const cell = await context.notebookEditor.joinNotebookCells(context.cell, direction, CellKind.Code); if (cell) { - context.notebookEditor.focusNotebookCell(cell, true); + context.notebookEditor.focusNotebookCell(cell, 'editor'); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index c96fc4299d1..a6ce7d26c13 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -224,7 +224,7 @@ export interface INotebookEditor { /** * Focus the container of a cell (the monaco editor inside is not focused). */ - focusNotebookCell(cell: ICellViewModel, focusEditor: boolean, focusOutput?: boolean): void; + focusNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void; /** * Execute the given notebook cell diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index b4158f27e2e..bf40cf8bf1d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -1116,15 +1116,15 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } } - focusNotebookCell(cell: ICellViewModel, focusEditor: boolean, focusOuput?: boolean) { - if (focusEditor) { + focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') { + if (focusItem === 'editor') { this.selectElement(cell); this.list?.focusView(); cell.editState = CellEditState.Editing; cell.focusMode = CellFocusMode.Editor; this.revealInCenterIfOutsideViewport(cell); - } else if (focusOuput) { + } else if (focusItem === 'output') { this.selectElement(cell); this.list?.focusView(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 15c5dc6e5d8..c079e42e9a9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -472,6 +472,7 @@ ${loaderJs} type: 'focus-output', id: this.activeCellId }); + this.activeCellId = undefined; } }); @@ -528,9 +529,9 @@ ${loaderJs} return; } - this.notebookEditor.focusNotebookCell(newCell, true); + this.notebookEditor.focusNotebookCell(newCell, 'editor'); } else { - this.notebookEditor.focusNotebookCell(info.cell, true); + this.notebookEditor.focusNotebookCell(info.cell, 'editor'); } } } 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 40fcca5f469..deda7daf6db 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // eslint-disable-next-line code-import-patterns -import 'vs/css!vs/workbench/contrib/notebook/browser/media/notebook'; import { getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; @@ -21,11 +20,13 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc import { deepClone } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { escape } from 'vs/base/common/strings'; +import 'vs/css!vs/workbench/contrib/notebook/browser/media/notebook'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { EditorOption, EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { Range } from 'vs/editor/common/core/range'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; @@ -37,6 +38,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +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, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; @@ -50,8 +52,6 @@ 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, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; const $ = DOM.$; @@ -586,14 +586,14 @@ export class CellDragAndDropController extends Disposable { private moveCell(draggedCell: ICellViewModel, ontoCell: ICellViewModel, direction: 'above' | 'below') { const editState = draggedCell.editState; this.notebookEditor.moveCell(draggedCell, ontoCell, direction); - this.notebookEditor.focusNotebookCell(draggedCell, editState === CellEditState.Editing); + this.notebookEditor.focusNotebookCell(draggedCell, editState === CellEditState.Editing ? 'editor' : 'container'); } private copyCell(draggedCell: ICellViewModel, ontoCell: ICellViewModel, direction: 'above' | 'below') { const editState = draggedCell.editState; const newCell = this.notebookEditor.insertNotebookCell(ontoCell, draggedCell.cellKind, direction, draggedCell.getText()); if (newCell) { - this.notebookEditor.focusNotebookCell(newCell, editState === CellEditState.Editing); + this.notebookEditor.focusNotebookCell(newCell, editState === CellEditState.Editing ? 'editor' : 'container'); } } diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index ec36e6928c8..2150569dee7 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -171,7 +171,7 @@ export class TestNotebookEditor implements INotebookEditor { saveNotebookCell(cell: CellViewModel): void { // throw new Error('Method not implemented.'); } - focusNotebookCell(cell: CellViewModel, focusEditor: boolean, focusOutput?: boolean): void { + focusNotebookCell(cell: CellViewModel, focusItem: 'editor' | 'container' | 'output'): void { // throw new Error('Method not implemented.'); } getActiveCell(): CellViewModel | undefined { From 58d63ccb079bc7cd155c955bce48a394b1aac043 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 7 May 2020 16:08:34 -0700 Subject: [PATCH 028/148] Stabalize reusePriorSearchConfiguration setting. Closes #89353. --- .../workbench/contrib/search/browser/search.contribution.ts | 5 +++++ .../contrib/searchEditor/browser/searchEditorInput.ts | 2 +- src/vs/workbench/services/search/common/search.ts | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index b3136e0a0ab..6f28920018f 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -826,6 +826,11 @@ configurationRegistry.registerConfiguration({ ], markdownDescription: nls.localize('search.searchEditor.doubleClickBehaviour', "Configure effect of double clicking a result in a search editor.") }, + 'search.searchEditor.reusePriorSearchConfiguration': { + type: 'boolean', + default: false, + markdownDescription: nls.localize('search.searchEditor.reusePriorSearchConfiguration', "When enabled, new Search Editors will reuse the includes, excludes, and flags of the previously opened Search Editor") + }, 'search.sortOrder': { 'type': 'string', 'enum': [SearchSortOrder.Default, SearchSortOrder.FileNames, SearchSortOrder.Type, SearchSortOrder.Modified, SearchSortOrder.CountDescending, SearchSortOrder.CountAscending], diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index cae18d4f411..b2d8dc6834c 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -304,7 +304,7 @@ export const getOrMakeSearchEditorInput = ( const storageService = accessor.get(IStorageService); const configurationService = accessor.get(IConfigurationService); - const reuseOldSettings = configurationService.getValue('search').searchEditor?.experimental?.reusePriorSearchConfiguration; + const reuseOldSettings = configurationService.getValue('search').searchEditor?.reusePriorSearchConfiguration; const priorConfig: SearchConfiguration = reuseOldSettings ? new Memento(SearchEditorInput.ID, storageService).getMemento(StorageScope.WORKSPACE).searchConfig : {}; const defaultConfig = defaultSearchConfig(); let config = { ...defaultConfig, ...priorConfig, ...existingData.config }; diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 19af00e19f8..68ebc39b1d2 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -349,7 +349,8 @@ export interface ISearchConfigurationProperties { searchOnTypeDebouncePeriod: number; searchEditor: { doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide', - experimental: { reusePriorSearchConfiguration: boolean } + reusePriorSearchConfiguration: boolean, + experimental: {} }; sortOrder: SearchSortOrder; } From 0a4d76def62ce7ed62578af57e1312362bb81dfa Mon Sep 17 00:00:00 2001 From: Christopher Maynard Date: Thu, 7 May 2020 19:10:26 -0400 Subject: [PATCH 029/148] Removed extra imports made by merge --- src/vs/workbench/contrib/notebook/browser/notebookEditor.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 645deeda049..929dbc680d9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -18,6 +18,7 @@ import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; +import { IReadonlyTextBuffer } from 'vs/editor/common/model'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -47,10 +48,6 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IPosition, Position } from 'vs/editor/common/core/position'; -import { IReadonlyTextBuffer } from 'vs/editor/common/model'; const $ = DOM.$; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; From e39f295cf717f51bb5577dfd61e862d0af088b8e Mon Sep 17 00:00:00 2001 From: Leila Pearson <873990+leilapearson@users.noreply.github.com> Date: Thu, 7 May 2020 16:43:52 -0700 Subject: [PATCH 030/148] Fix filename sort order edge cases in explorer. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Partially addresses issue #27759 Adds compareFileNamesNumeric() and compareFileExtensionsNumeric() funcitons, and uses the new functions in explorer view. Fixes the following edge cases: 1. Sorts the same name with different case in locale order (a < A) instead of unicode order (A < a). 2. Sorts the same name with different accents or diacritics by locale (á < à) instead of in unicode order (à < á) . 3. Sorts names that are equivalent in a numeric locale comparison as shortest numeric representation first (1 < 01 < 001) instead of unicode order (001 < 01 < 1). 4. For filenames with a name plus an extension, compares names first fand then compares extensions (aggregate.go < aggregate_repo.go) instead of comparing the whole name at once (aggregate_repo.go < aggregate.go) 5. Treats dotfiles (filenames that start with a dot) as filenames without extensions, instead of either as an empty filename plus an extension (single-dot dotfile), or a filename that starts with a dot plus an extension (multi-dot dotfile). This means all dotfiles group together in their own section when sorting by file type, (.eslintignore < .eslintrc.json < file.cs < file.js < file.json) instead of being interspersed between files grouped by type (file.cs < .eslintignore < file.js < .eslintrc.json < file.json). Note: other areas of workbench may benefit from the new functions as well but only explorer has been changed so far. --- src/vs/base/common/comparers.ts | 124 +++++++- src/vs/base/test/browser/comparers.test.ts | 266 +++++++++++++++++- .../files/browser/views/explorerViewer.ts | 10 +- 3 files changed, 376 insertions(+), 24 deletions(-) diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index a713092dbda..4696e91bb54 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -6,7 +6,12 @@ import { sep } from 'vs/base/common/path'; import { IdleValue } from 'vs/base/common/async'; -const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => { +// When comparing large numbers of strings, such as in sorting large arrays, is better for +// performance to create an Intl.Collator object and use the function provided by its compare +// property than it is to use String.prototype.localeCompare() + +// A collator with numeric sorting enabled, and no sensitivity to case or to accents +const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => { const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); return { collator: collator, @@ -14,20 +19,74 @@ const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumer }; }); +// A collator with numeric sorting enabled. +const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => { + const collator = new Intl.Collator(undefined, { numeric: true }); + return { + collator: collator + }; +}); + +// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case. +const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => { + const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' }); + return { + collator: collator + }; +}); + export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number { const a = one || ''; const b = other || ''; - const result = intlFileNameCollator.getValue().collator.compare(a, b); + const result = intlFileNameCollatorBaseNumeric.getValue().collator.compare(a, b); // Using the numeric option in the collator will // make compare(`foo1`, `foo01`) === 0. We must disambiguate. - if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && a !== b) { + if (intlFileNameCollatorBaseNumeric.getValue().collatorIsNumeric && result === 0 && a !== b) { return a < b ? -1 : 1; } return result; } +/** Compares filenames by name then extension, sorting numbers numerically instead of alphabetically. */ +export function compareFileNamesNumeric(one: string | null, other: string | null): number { + const [oneName, oneExtension] = extractNameAndExtension(one, true); + const [otherName, otherExtension] = extractNameAndExtension(other, true); + const collatorNumeric = intlFileNameCollatorNumeric.getValue().collator; + const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.getValue().collator; + let result; + + // Check for name differences, comparing numbers numerically instead of alphabetically. + result = collatorNumeric.compare(oneName, otherName); + if (result !== 0) { + return result; + } + + // Using the numeric option in the collator will make compare(`foo1`, `foo01`) === 0. Sort the shorter name first. + if (oneName.length !== otherName.length) { + return oneName.length < otherName.length ? -1 : 1; + } + + // Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically. + result = collatorNumericCaseInsensitive.compare(oneExtension, otherExtension); + if (result !== 0) { + return result; + } + + // If extensions are numerically equal but not equal in length, sort the shorter extension first. + if (oneExtension.length !== otherExtension.length) { + return oneExtension.length < otherExtension.length ? -1 : 1; + } + + // Disambiguate the extension case if needed. + if (oneExtension !== otherExtension) { + return collatorNumeric.compare(oneExtension, otherExtension); + } + + return 0; +} + const FileNameMatch = /^(.*?)(\.([^.]*))?$/; export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number { @@ -54,19 +113,19 @@ export function compareFileExtensions(one: string | null, other: string | null): const [oneName, oneExtension] = extractNameAndExtension(one); const [otherName, otherExtension] = extractNameAndExtension(other); - let result = intlFileNameCollator.getValue().collator.compare(oneExtension, otherExtension); + let result = intlFileNameCollatorBaseNumeric.getValue().collator.compare(oneExtension, otherExtension); if (result === 0) { // Using the numeric option in the collator will // make compare(`foo1`, `foo01`) === 0. We must disambiguate. - if (intlFileNameCollator.getValue().collatorIsNumeric && oneExtension !== otherExtension) { + if (intlFileNameCollatorBaseNumeric.getValue().collatorIsNumeric && oneExtension !== otherExtension) { return oneExtension < otherExtension ? -1 : 1; } // Extensions are equal, compare filenames - result = intlFileNameCollator.getValue().collator.compare(oneName, otherName); + result = intlFileNameCollatorBaseNumeric.getValue().collator.compare(oneName, otherName); - if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) { + if (intlFileNameCollatorBaseNumeric.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) { return oneName < otherName ? -1 : 1; } } @@ -74,10 +133,57 @@ export function compareFileExtensions(one: string | null, other: string | null): return result; } -function extractNameAndExtension(str?: string | null): [string, string] { +/** Compares filenames by extenson, then by name. Sorts numbers numerically, not alphabetically. */ +export function compareFileExtensionsNumeric(one: string | null, other: string | null): number { + const [oneName, oneExtension] = extractNameAndExtension(one, true); + const [otherName, otherExtension] = extractNameAndExtension(other, true); + const collatorNumeric = intlFileNameCollatorNumeric.getValue().collator; + const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.getValue().collator; + let result; + + // Check for extension differences, ignoring differences in case and comparing numbers numerically. + result = collatorNumericCaseInsensitive.compare(oneExtension, otherExtension); + if (result !== 0) { + return result; + } + + // Disambiguate equivalent numbers in extensions. + if (oneExtension.length !== otherExtension.length) { + return oneExtension.length < otherExtension.length ? -1 : 1; + } + + // Compare names. + result = collatorNumeric.compare(oneName, otherName); + if (result !== 0) { + return result; + } + + // Disambiguate equivalent numbers in names. + if (oneName.length !== otherName.length) { + return oneName.length < otherName.length ? -1 : 1; + } + + // Disambiguate extension case if needed. + if (oneExtension !== otherExtension) { + return collatorNumeric.compare(oneExtension, otherExtension); + } + + return 0; +} + +/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */ +export function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] { const match = str ? FileNameMatch.exec(str) as Array : ([] as Array); - return [(match && match[1]) || '', (match && match[3]) || '']; + let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || '']; + + // if the dotfilesAsNames option is selected, treat an empty filename with an extension, + // or a filename that starts with a dot, as a dotfile name + if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) { + result = [result[0] + '.' + result[1], '']; + } + + return result; } function comparePathComponents(one: string, other: string, caseSensitive = false): number { diff --git a/src/vs/base/test/browser/comparers.test.ts b/src/vs/base/test/browser/comparers.test.ts index 369f160803b..90dff8c2835 100644 --- a/src/vs/base/test/browser/comparers.test.ts +++ b/src/vs/base/test/browser/comparers.test.ts @@ -3,48 +3,294 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareFileNames, compareFileExtensions } from 'vs/base/common/comparers'; +import { compareFileNames, compareFileExtensions, compareFileNamesNumeric, compareFileExtensionsNumeric } from 'vs/base/common/comparers'; import * as assert from 'assert'; +const compareLocale = (a: string, b: string) => a.localeCompare(b); +const compareLocaleNumeric = (a: string, b: string) => a.localeCompare(b, undefined, { numeric: true }); + + suite('Comparers', () => { test('compareFileNames', () => { + // + // Comparisons with the same results as compareFileNamesNumeric + // + + // name-only comparisons assert(compareFileNames(null, null) === 0, 'null should be equal'); assert(compareFileNames(null, 'abc') < 0, 'null should be come before real values'); assert(compareFileNames('', '') === 0, 'empty should be equal'); assert(compareFileNames('abc', 'abc') === 0, 'equal names should be equal'); - assert(compareFileNames('.abc', '.abc') === 0, 'equal full names should be equal'); - assert(compareFileNames('.env', '.env.example') < 0, 'filenames with extensions should come after those without'); - assert(compareFileNames('.env.example', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly'); + assert(compareFileNames('z', 'A') > 0, 'z comes is after A regardless of case'); + assert(compareFileNames('Z', 'a') > 0, 'Z comes after a regardless of case'); + + // name plus extension comparisons + assert(compareFileNames('bbb.aaa', 'aaa.bbb') > 0, 'files with extensions are compared first by filename'); + + // dotfile comparisons + assert(compareFileNames('.abc', '.abc') === 0, 'equal dotfile names should be equal'); + assert(compareFileNames('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly'); + assert(compareFileNames('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots'); + assert(compareFileNames('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first'); + assert(compareFileNames('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot'); + + // dotfile vs non-dotfile comparisons + assert(compareFileNames(null, '.abc') < 0, 'null should come before dotfiles'); + assert(compareFileNames('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions'); + assert(compareFileNames('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions'); + assert(compareFileNames('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files'); + assert(compareFileNames('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files'); + + // numeric comparisons assert(compareFileNames('1', '1') === 0, 'numerically equal full names should be equal'); assert(compareFileNames('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); assert(compareFileNames('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); assert(compareFileNames('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long'); + assert(compareFileNames('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically'); + assert(compareFileNames('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); + + // + // Comparisons with different results than compareFileNamesNumeric + // + + // name-only comparisons + assert(compareFileNames('a', 'A') !== compareLocale('a', 'A'), 'the same letter does not sort by locale'); + assert(compareFileNames('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter does not sort by locale'); + assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNames), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order'); + assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNames), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents do not sort in locale order'); + + // name plus extension comparisons + assert(compareFileNames('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole name all at once by locale'); + + // numeric comparisons + assert(compareFileNames('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order'); + assert(compareFileNames('abc.txt1', 'abc.txt01') > 0, 'same name plus extensions with equal numbers sort in unicode order'); + assert(compareFileNames('art01', 'Art01') !== 'art01'.localeCompare('Art01', undefined, { numeric: true }), + 'a numerically equivalent word of a different case does not compare numerically based on locale'); + }); test('compareFileExtensions', () => { + // + // Comparisons with the same results as compareFileExtensionsNumeric + // + + // name-only comparisons assert(compareFileExtensions(null, null) === 0, 'null should be equal'); - assert(compareFileExtensions(null, '.abc') < 0, 'null should come before real files'); assert(compareFileExtensions(null, 'abc') < 0, 'null should come before real files without extension'); assert(compareFileExtensions('', '') === 0, 'empty should be equal'); assert(compareFileExtensions('abc', 'abc') === 0, 'equal names should be equal'); - assert(compareFileExtensions('.abc', '.abc') === 0, 'equal full names should be equal'); + assert(compareFileExtensions('z', 'A') > 0, 'z comes after A'); + assert(compareFileExtensions('Z', 'a') > 0, 'Z comes after a'); + + // name plus extension comparisons assert(compareFileExtensions('file.ext', 'file.ext') === 0, 'equal full names should be equal'); assert(compareFileExtensions('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared'); - assert(compareFileExtensions('.ext', 'a.ext') < 0, 'if equal extensions, filenames should be compared, empty filename should come before others'); - assert(compareFileExtensions('file.aaa', 'file.bbb') < 0, 'files should be compared by extensions'); + assert(compareFileExtensions('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions'); assert(compareFileExtensions('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extensions even if filenames compare differently'); + assert(compareFileExtensions('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names'); + assert(compareFileExtensions('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore'); + assert(compareFileExtensions('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name'); + + // dotfile comparisons + assert(compareFileExtensions('.abc', '.abc') === 0, 'equal dotfiles should be equal'); + assert(compareFileExtensions('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case'); + + // dotfile vs non-dotfile comparisons + assert(compareFileExtensions(null, '.abc') < 0, 'null should come before dotfiles'); + assert(compareFileExtensions('.env', 'aaa.env') < 0, 'if equal extensions, filenames should be compared, empty filename should come before others'); + assert(compareFileExtensions('.MD', 'a.md') < 0, 'if extensions differ in case, files sort by extension in unicode order'); + + // numeric comparisons assert(compareFileExtensions('1', '1') === 0, 'numerically equal full names should be equal'); assert(compareFileExtensions('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); assert(compareFileExtensions('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); assert(compareFileExtensions('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long'); + assert(compareFileExtensions('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically'); + assert(compareFileExtensions('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); + assert(compareFileExtensions('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); assert(compareFileExtensions('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal'); assert(compareFileExtensions('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); assert(compareFileExtensions('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long'); assert(compareFileExtensions('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared'); - assert(compareFileExtensions('file2.ext2', 'file1.ext10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); - assert(compareFileExtensions('file.ext01', 'file.ext1') < 0, 'extensions with equal numbers should be in alphabetical order'); + assert(compareFileExtensions('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically'); + + // Same extension comparison that has the same result as compareFileExtensionsNumeric, but a different result than compareFileNames + // This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension. + assert(compareFileExtensions('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order'); + + // + // Comparisons with different results from compareFileExtensionsNumeric + // + + // name-only comparisions + assert(compareFileExtensions('a', 'A') !== compareLocale('a', 'A'), 'the same letter of different case does not sort by locale'); + assert(compareFileExtensions('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter of different case does not sort by locale'); + assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensions), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order'); + assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensions), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents do not sort in locale order'); + + // name plus extension comparisons + assert(compareFileExtensions('a.MD', 'a.md') !== compareLocale('MD', 'md'), 'case differences in extensions do not sort by locale'); + assert(compareFileExtensions('a.md', 'A.md') !== compareLocale('a', 'A'), 'case differences in names do not sort by locale'); + + // dotfile comparisons + assert(compareFileExtensions('.env', '.aaa.env') < 0, 'a dotfile with an extension is treated as a name plus an extension - equal extensions'); + assert(compareFileExtensions('.env', '.env.aaa') > 0, 'a dotfile with an extension is treated as a name plus an extension - unequal extensions'); + + // dotfile vs non-dotfile comparisons + assert(compareFileExtensions('.env', 'aaa') > 0, 'filenames without extensions come before dotfiles'); + assert(compareFileExtensions('.md', 'A.MD') > 0, 'a file with an uppercase extension sorts before a dotfile of the same lowercase extension'); + + // numeric comparisons + assert(compareFileExtensions('abc.txt01', 'abc.txt1') < 0, 'extensions with equal numbers sort in unicode order'); + assert(compareFileExtensions('art01', 'Art01') !== compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case does not compare by locale'); + assert(compareFileExtensions('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order'); + assert(compareFileExtensions('txt.abc01', 'txt.abc1') < 0, 'extensions with equivalent numbers sort in unicode order'); + + }); + + test('compareFileNamesNumeric', () => { + + // + // Comparisons with the same results as compareFileNames + // + + // name-only comparisons + assert(compareFileNamesNumeric(null, null) === 0, 'null should be equal'); + assert(compareFileNamesNumeric(null, 'abc') < 0, 'null should be come before real values'); + assert(compareFileNamesNumeric('', '') === 0, 'empty should be equal'); + assert(compareFileNamesNumeric('abc', 'abc') === 0, 'equal names should be equal'); + assert(compareFileNamesNumeric('z', 'A') > 0, 'z comes is after A regardless of case'); + assert(compareFileNamesNumeric('Z', 'a') > 0, 'Z comes after a regardless of case'); + + // name plus extension comparisons + assert(compareFileNamesNumeric('file.ext', 'file.ext') === 0, 'equal full names should be equal'); + assert(compareFileNamesNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared'); + assert(compareFileNamesNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions'); + assert(compareFileNamesNumeric('bbb.aaa', 'aaa.bbb') > 0, 'files should be compared by names even if extensions compare differently'); + + // dotfile comparisons + assert(compareFileNamesNumeric('.abc', '.abc') === 0, 'equal dotfile names should be equal'); + assert(compareFileNamesNumeric('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly'); + assert(compareFileNamesNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots'); + assert(compareFileNamesNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first'); + assert(compareFileNamesNumeric('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot'); + + // dotfile vs non-dotfile comparisons + assert(compareFileNamesNumeric(null, '.abc') < 0, 'null should come before dotfiles'); + assert(compareFileNamesNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions'); + assert(compareFileNamesNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions'); + assert(compareFileNamesNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files'); + assert(compareFileNamesNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files'); + + // numeric comparisons + assert(compareFileNamesNumeric('1', '1') === 0, 'numerically equal full names should be equal'); + assert(compareFileNamesNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); + assert(compareFileNamesNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); + assert(compareFileNamesNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long'); + assert(compareFileNamesNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically'); + assert(compareFileNamesNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); + + // + // Comparisons with different results than compareFileNames + // + + // name-only comparisons + assert(compareFileNamesNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale'); + assert(compareFileNamesNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale'); + assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); + assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesNumeric), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order'); + + // name plus extensions comparisons + assert(compareFileNamesNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'compares the name first, then the extension'); + + // numeric comparisons + assert(compareFileNamesNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest number first'); + assert(compareFileNamesNumeric('abc.txt1', 'abc.txt01') < 0, 'same name plus extensions with equal numbers sort shortest number first'); + assert(compareFileNamesNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale'); + + }); + + test('compareFileExtensionsNumeric', () => { + + // + // Comparisons with the same result as compareFileExtensions + // + + // name-only comparisons + assert(compareFileExtensionsNumeric(null, null) === 0, 'null should be equal'); + assert(compareFileExtensionsNumeric(null, 'abc') < 0, 'null should come before real files without extensions'); + assert(compareFileExtensionsNumeric('', '') === 0, 'empty should be equal'); + assert(compareFileExtensionsNumeric('abc', 'abc') === 0, 'equal names should be equal'); + assert(compareFileExtensionsNumeric('z', 'A') > 0, 'z comes after A'); + assert(compareFileExtensionsNumeric('Z', 'a') > 0, 'Z comes after a'); + + // name plus extension comparisons + assert(compareFileExtensionsNumeric('file.ext', 'file.ext') === 0, 'equal full filenames should be equal'); + assert(compareFileExtensionsNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared'); + assert(compareFileExtensionsNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions'); + assert(compareFileExtensionsNumeric('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extension first'); + assert(compareFileExtensionsNumeric('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names'); + assert(compareFileExtensionsNumeric('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore'); + assert(compareFileExtensionsNumeric('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name'); + + // dotfile comparisons + assert(compareFileExtensionsNumeric('.abc', '.abc') === 0, 'equal dotfiles should be equal'); + assert(compareFileExtensionsNumeric('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case'); + + // dotfile vs non-dotfile comparisons + assert(compareFileExtensionsNumeric(null, '.abc') < 0, 'null should come before dotfiles'); + assert(compareFileExtensionsNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions'); + assert(compareFileExtensionsNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files'); + + // numeric comparisons + assert(compareFileExtensionsNumeric('1', '1') === 0, 'numerically equal full names should be equal'); + assert(compareFileExtensionsNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); + assert(compareFileExtensionsNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); + assert(compareFileExtensionsNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order'); + assert(compareFileExtensionsNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically'); + assert(compareFileExtensionsNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number'); + assert(compareFileExtensionsNumeric('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); + assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal'); + assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); + assert(compareFileExtensionsNumeric('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long'); + assert(compareFileExtensionsNumeric('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared'); + assert(compareFileExtensionsNumeric('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically'); + + // Same extension comparison that has the same result as compareFileExtensions, but a different result than compareFileNames + // This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension. + assert(compareFileExtensionsNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order'); + + // + // Comparisons with different results than compareFileExtensions + // + + // name-only comparisons + assert(compareFileExtensionsNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale'); + assert(compareFileExtensionsNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale'); + assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order'); + assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsNumeric), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order'); + + // name plus extension comparisons + assert(compareFileExtensionsNumeric('a.MD', 'a.md') === compareLocale('MD', 'md'), 'case differences in extensions sort by locale'); + assert(compareFileExtensionsNumeric('a.md', 'A.md') === compareLocale('a', 'A'), 'case differences in names sort by locale'); + + // dotfile comparisons + assert(compareFileExtensionsNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots'); + assert(compareFileExtensionsNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first'); + + // dotfile vs non-dotfile comparisons + assert(compareFileExtensionsNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions'); + assert(compareFileExtensionsNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files'); + + // numeric comparisons + assert(compareFileExtensionsNumeric('abc.txt01', 'abc.txt1') > 0, 'extensions with equal numbers should be in shortest-first order'); + assert(compareFileExtensionsNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale'); + assert(compareFileExtensionsNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest string first'); + assert(compareFileExtensionsNumeric('txt.abc01', 'txt.abc1') > 0, 'extensions with equivalent numbers sort shortest extension first'); + }); }); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index a7484198393..7458682b6c6 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -29,7 +29,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { equals, deepClone } from 'vs/base/common/objects'; import * as path from 'vs/base/common/path'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; -import { compareFileExtensions, compareFileNames } from 'vs/base/common/comparers'; +import { compareFileExtensionsNumeric, compareFileNamesNumeric } from 'vs/base/common/comparers'; import { fillResourceDataTransfers, CodeDataTransfers, extractResources, containsDragType } from 'vs/workbench/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; @@ -654,7 +654,7 @@ export class FileSorter implements ITreeSorter { } if (statA.isDirectory && statB.isDirectory) { - return compareFileNames(statA.name, statB.name); + return compareFileNamesNumeric(statA.name, statB.name); } break; @@ -688,17 +688,17 @@ export class FileSorter implements ITreeSorter { // Sort Files switch (sortOrder) { case 'type': - return compareFileExtensions(statA.name, statB.name); + return compareFileExtensionsNumeric(statA.name, statB.name); case 'modified': if (statA.mtime !== statB.mtime) { return (statA.mtime && statB.mtime && statA.mtime < statB.mtime) ? 1 : -1; } - return compareFileNames(statA.name, statB.name); + return compareFileNamesNumeric(statA.name, statB.name); default: /* 'default', 'mixed', 'filesFirst' */ - return compareFileNames(statA.name, statB.name); + return compareFileNamesNumeric(statA.name, statB.name); } } } From 027b6b709ce043c5ad5c7fb7f02f9d1a793e046d Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 7 May 2020 16:46:14 -0700 Subject: [PATCH 031/148] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b4ea6e65d7..d2d1082d47b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.46.0", - "distro": "ce946b3269a7188e873f31dd642911089c3e03e7", + "distro": "6ec59b3610ac45e47ab420d15a0588817178a043", "author": { "name": "Microsoft Corporation" }, From c3fe074dadb287ebb36fcab8891da52dfedb1310 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Thu, 7 May 2020 18:19:24 -0700 Subject: [PATCH 032/148] Fix typo --- src/vs/workbench/browser/workbench.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 7c8c575f891..18196e719a3 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -34,7 +34,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio }, 'workbench.editor.scrollToSwitchTabs': { 'type': 'boolean', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls wether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration."), + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration."), 'default': false }, 'workbench.editor.highlightModifiedTabs': { From 7653d836944892f83ce9e1f95c1204bafa1aec31 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 7 May 2020 20:26:56 -0700 Subject: [PATCH 033/148] Update pills style to use mix (ref #96608) --- src/vs/base/browser/ui/countBadge/countBadge.css | 5 +++++ src/vs/base/parts/quickinput/browser/media/quickInput.css | 2 ++ src/vs/workbench/contrib/debug/browser/callStackView.ts | 2 +- .../contrib/files/browser/media/explorerviewlet.css | 3 +-- .../workbench/contrib/files/browser/views/openEditorsView.ts | 2 +- .../contrib/preferences/browser/keybindingsEditor.ts | 2 +- .../workbench/contrib/preferences/browser/settingsEditor2.ts | 2 +- 7 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css index db90e7ae552..ce26af5e6a1 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/src/vs/base/browser/ui/countBadge/countBadge.css @@ -15,3 +15,8 @@ display: inline-block; box-sizing: border-box; } + +.monaco-count-badge.long { + padding: 2px 3px; + border-radius: 2px; +} diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index d45840ee38c..c086b587c14 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -103,6 +103,8 @@ .quick-input-count .monaco-count-badge { vertical-align: middle; + padding: 2px 4px; + border-radius: 2px; } .quick-input-action { diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 4a1a948276a..9f65698bf2d 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -475,7 +475,7 @@ class SessionsRenderer implements ITreeRenderer { diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index f0078fb7192..b77e6fb7065 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -60,8 +60,7 @@ } .dirty-count.monaco-count-badge { - padding-top: 2px; - padding-bottom: 2px; + padding: 2px 4px; margin-left: 6px; min-height: auto; } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index f6dd1068a03..dfdbc4bac2c 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -184,7 +184,7 @@ export class OpenEditorsView extends ViewPane { super.renderHeaderTitle(container, this.title); const count = dom.append(container, $('.count')); - this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge')); + this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge.long')); this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index f20dcdfbcbe..8b7b0145b91 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -401,7 +401,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditorP } private createRecordingBadge(container: HTMLElement): HTMLElement { - const recordingBadge = DOM.append(container, DOM.$('.recording-badge.monaco-count-badge.disabled')); + const recordingBadge = DOM.append(container, DOM.$('.recording-badge.monaco-count-badge.long.disabled')); recordingBadge.textContent = localize('recording', "Recording Keys"); this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 9a7443f2aaf..f7fbfb25ba0 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -442,7 +442,7 @@ export class SettingsEditor2 extends BaseEditor { inputBorder: settingsTextInputBorder })); - this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget.monaco-count-badge')); + this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget.monaco-count-badge.long')); this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; const border = colors.contrastBorder ? colors.contrastBorder.toString() : ''; From 004282afb048d2d6a92e380ea6dbf9d7afd809bd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 May 2020 08:14:57 +0200 Subject: [PATCH 034/148] diag - start crash reporter as early as possible if --crash-reporter-directory is used --- src/main.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main.js b/src/main.js index 9309c12a747..23915126b8f 100644 --- a/src/main.js +++ b/src/main.js @@ -35,6 +35,9 @@ app.setPath('userData', userDataPath); // Set temp directory based on crash-reporter-directory CLI argument // The crash reporter will store crashes in temp folder so we need // to change that location accordingly. + +// If a crash-reporter-directory is specified we setup the crash reporter +// right from the beginning as early as possible to monitor all processes. let crashReporterDirectory = args['crash-reporter-directory']; if (crashReporterDirectory) { crashReporterDirectory = path.normalize(crashReporterDirectory); @@ -47,8 +50,20 @@ if (crashReporterDirectory) { app.exit(1); } } + + // Crashes are stored in the temp directory by default, so we + // need to change that directory to the provided one console.log(`Found --crash-reporter-directory argument. Setting temp directory to be '${crashReporterDirectory}'`); app.setPath('temp', crashReporterDirectory); + + // Start crash reporter + const { crashReporter } = require('electron'); + crashReporter.start({ + companyName: 'Microsoft', + productName: product.nameShort, + submitURL: '', + uploadToServer: false + }); } // Set logs path before app 'ready' event if running portable From ff118340b561fea6184172ffaf3bbf859b21055b Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 7 May 2020 23:21:40 -0700 Subject: [PATCH 035/148] Use system-ui alias for UI fonts (fixes #10144) (#96948) Commit 45d93e93255f9cdaa37955fdb79b4e460252b45b applied this change in some areas, but it was reverted to fix #28619. The underlying cause of the regression was Chromium bug 733219 [1], which has now been fixed, so this change should be safe to apply now. The old font stacks have been kept with lower priorities to work around Chromium bug 724393 [2]. [1] https://bugs.chromium.org/p/chromium/issues/detail?id=733219 [2] https://bugs.chromium.org/p/chromium/issues/detail?id=724393 --- .../media/markdown.css | 2 +- .../markdown-language-features/package.json | 2 +- .../issue/media/issueReporter.css | 10 +++---- .../processExplorer/media/processExplorer.css | 10 +++---- src/vs/code/electron-browser/proxy/auth.html | 4 +-- .../standalone/browser/standalone-tokens.css | 2 +- src/vs/workbench/browser/media/style.css | 30 +++++++++---------- .../suggestEnabledInput.ts | 2 +- .../contrib/scm/browser/repositoryPane.ts | 2 +- .../contrib/webview/common/themeing.ts | 2 +- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/extensions/markdown-language-features/media/markdown.css b/extensions/markdown-language-features/media/markdown.css index 5c06f2b082f..4facd2fbb94 100644 --- a/extensions/markdown-language-features/media/markdown.css +++ b/extensions/markdown-language-features/media/markdown.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ html, body { - font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif); + font-family: var(--vscode-markdown-font-family, system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif); font-size: var(--vscode-markdown-font-size, 14px); padding: 0 26px; line-height: var(--vscode-markdown-line-height, 22px); diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 541cd4e327a..fcefaef0a56 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -210,7 +210,7 @@ }, "markdown.preview.fontFamily": { "type": "string", - "default": "-apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif", + "default": "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif", "description": "%markdown.preview.fontFamily.desc%", "scope": "resource" }, diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-browser/issue/media/issueReporter.css index eb4f53747a8..ad29591ada0 100644 --- a/src/vs/code/electron-browser/issue/media/issueReporter.css +++ b/src/vs/code/electron-browser/issue/media/issueReporter.css @@ -95,25 +95,25 @@ textarea, input, select { } html { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; color: #CCCCCC; height: 100%; } html:lang(zh-Hans) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } html:lang(zh-Hant) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif; } html:lang(ja) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; } html:lang(ko) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } body { diff --git a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css index 69b0b9a35c9..606ec4c84a8 100644 --- a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css +++ b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css @@ -4,24 +4,24 @@ *--------------------------------------------------------------------------------------------*/ html { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; font-size: 13px; } html:lang(zh-Hans) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } html:lang(zh-Hant) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif; } html:lang(ja) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; } html:lang(ko) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } body { diff --git a/src/vs/code/electron-browser/proxy/auth.html b/src/vs/code/electron-browser/proxy/auth.html index 5ef195878ca..7596a20dbd3 100644 --- a/src/vs/code/electron-browser/proxy/auth.html +++ b/src/vs/code/electron-browser/proxy/auth.html @@ -18,7 +18,7 @@ } body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; font-size: 10pt; background-color: #F3F3F3; } @@ -58,7 +58,7 @@ } input { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important; } diff --git a/src/vs/editor/standalone/browser/standalone-tokens.css b/src/vs/editor/standalone/browser/standalone-tokens.css index 6cc916c0aa2..4d173457365 100644 --- a/src/vs/editor/standalone/browser/standalone-tokens.css +++ b/src/vs/editor/standalone/browser/standalone-tokens.css @@ -6,7 +6,7 @@ /* Default standalone editor font */ .monaco-editor { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif; } .monaco-menu .monaco-action-bar.vertical .action-item .action-menu-item:focus .action-label { diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index d63b7af49f4..51850da3706 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -5,23 +5,23 @@ /* Font Families (with CJK support) */ -.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } -.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } -.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } -.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } -.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; } +.mac { font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif; } +.mac:lang(zh-Hans) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } +.mac:lang(zh-Hant) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } +.mac:lang(ja) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } +.mac:lang(ko) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; } -.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; } -.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } -.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } -.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; } -.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } +.windows { font-family: system-ui, "Segoe WPC", "Segoe UI", sans-serif; } +.windows:lang(zh-Hans) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } +.windows:lang(zh-Hant) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } +.windows:lang(ja) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; } +.windows:lang(ko) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } -.linux { font-family: "Ubuntu", "Droid Sans", sans-serif; } -.linux:lang(zh-Hans) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } -.linux:lang(zh-Hant) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } -.linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } -.linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } +.linux { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; } +.linux:lang(zh-Hans) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } +.linux:lang(zh-Hant) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } +.linux:lang(ja) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } +.linux:lang(ko) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } .mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Courier, monospace; } .windows { --monaco-monospace-font: Consolas, "Courier New", monospace; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 045c2470162..c1201838060 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -311,7 +311,7 @@ function getSuggestEnabledInputOptions(ariaLabel?: string): IEditorOptions { roundedSelection: false, renderIndentGuides: false, cursorWidth: 1, - fontFamily: ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', + fontFamily: ' system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', ariaLabel: ariaLabel || '', snippetSuggestions: 'none', diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 2b1b9a3d03a..0ca05abcd97 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -766,7 +766,7 @@ export class RepositoryPane extends ViewPane { cursorWidth: 1, fontSize: 13, lineHeight: 20, - fontFamily: ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', + fontFamily: ' system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', wrappingStrategy: 'advanced', wrappingIndent: 'none', padding: { top: 3, bottom: 3 }, diff --git a/src/vs/workbench/contrib/webview/common/themeing.ts b/src/vs/workbench/contrib/webview/common/themeing.ts index cd8e73fafc4..5d66fb268ef 100644 --- a/src/vs/workbench/contrib/webview/common/themeing.ts +++ b/src/vs/workbench/contrib/webview/common/themeing.ts @@ -63,7 +63,7 @@ export class WebviewThemeDataProvider extends Disposable { }, {} as { [key: string]: string; }); const styles = { - 'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', + 'vscode-font-family': 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', 'vscode-font-weight': 'normal', 'vscode-font-size': '13px', 'vscode-editor-font-family': editorFontFamily, From 4340b8b5c45e8e1dcd80483d348cf8a81e7d7f3b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 May 2020 10:57:02 +0200 Subject: [PATCH 036/148] editors - for now disable sticky index when tabs get disabled --- src/vs/workbench/common/editor/editorGroup.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 13661dd8d18..537d857d717 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -122,6 +122,12 @@ export class EditorGroup extends Disposable { private onConfigurationUpdated(): void { this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning'); this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose'); + + if (this.configurationService.getValue('workbench.editor.showTabs') === false) { + // Disabling tabs disables sticky editors until we support + // an indication of sticky editors when tabs are disabled + this.sticky = -1; + } } get count(): number { From 709a3508b2458b37ef03e00847b3678e0dad0092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 8 May 2020 11:22:20 +0200 Subject: [PATCH 037/148] fixes #97228 --- src/vs/base/browser/ui/list/listPaging.ts | 6 +++--- src/vs/platform/list/browser/listService.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index d0e5097f9e6..508fb3dcabd 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -165,7 +165,7 @@ export class PagedList implements IDisposable { } get onDidChangeFocus(): Event> { - return Event.map(this.list.onDidChangeFocus, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } get onDidOpen(): Event> { @@ -173,11 +173,11 @@ export class PagedList implements IDisposable { } get onDidChangeSelection(): Event> { - return Event.map(this.list.onDidChangeSelection, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } get onPin(): Event> { - return Event.map(this.list.onDidPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + return Event.map(this.list.onDidPin, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } get onContextMenu(): Event> { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index f346cd655fd..f81f8534749 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -531,7 +531,7 @@ abstract class ResourceNavigator extends Disposable { !!(browserEvent).preserveFocus : !isDoubleClick; - if (this.treeOrList.openOnSingleClick || isDoubleClick || isKeyboardEvent) { + if (this.options.openOnSingleClick || this.treeOrList.openOnSingleClick || isDoubleClick || isKeyboardEvent) { const sideBySide = browserEvent instanceof MouseEvent && (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey); this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, browserEvent); } From 161d93bb787df7c95f5a24f6ef83e28c93967bfa Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 May 2020 11:31:08 +0200 Subject: [PATCH 038/148] build - publish crash reports only if previous steps failed and continue on error --- build/azure-pipelines/darwin/continuous-build-darwin.yml | 3 ++- build/azure-pipelines/darwin/product-build-darwin.yml | 3 ++- build/azure-pipelines/linux/continuous-build-linux.yml | 5 +++-- build/azure-pipelines/linux/product-build-linux.yml | 3 ++- build/azure-pipelines/win32/continuous-build-win32.yml | 3 ++- build/azure-pipelines/win32/product-build-win32.yml | 3 ++- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index 0733d47bc72..9f66907f0a1 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -66,7 +66,8 @@ steps: artifactName: crash-dump-macos targetPath: .build/crashes displayName: 'Publish Crash Reports' - condition: succeededOrFailed() + continueOnError: true + condition: failed() - task: PublishTestResults@2 displayName: Publish Tests Results diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 32bfd2c83db..223fb3377d7 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -157,7 +157,8 @@ steps: artifactName: crash-dump-macos targetPath: .build/crashes displayName: 'Publish Crash Reports' - condition: succeededOrFailed() + continueOnError: true + condition: failed() - script: | set -e diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index fb0a5b2dd3e..fdd4c305cda 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -75,8 +75,9 @@ steps: artifactName: crash-dump-linux targetPath: .build/crashes displayName: 'Publish Crash Reports' - condition: succeededOrFailed() - + continueOnError: true + condition: failed() + - task: PublishTestResults@2 displayName: Publish Tests Results inputs: diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 76428b860f2..f28c896ba83 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -145,7 +145,8 @@ steps: artifactName: crash-dump-linux targetPath: .build/crashes displayName: 'Publish Crash Reports' - condition: succeededOrFailed() + continueOnError: true + condition: failed() - script: | set -e diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index 57385d54299..b0a81aeb8c8 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -73,7 +73,8 @@ steps: inputs: artifactName: crash-dump-windows targetPath: .build\crashes - condition: succeededOrFailed() + continueOnError: true + condition: failed() - task: PublishTestResults@2 displayName: Publish Tests Results diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 75dc54e3599..bcd03489df6 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -154,7 +154,8 @@ steps: artifactName: crash-dump-windows-$(VSCODE_ARCH) targetPath: .build\crashes displayName: 'Publish Crash Reports' - condition: succeededOrFailed() + continueOnError: true + condition: failed() - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: From abc9127dc0d70ef25b64759a2381ea403712e550 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 8 May 2020 11:43:29 +0200 Subject: [PATCH 039/148] Fix #97125 --- src/vs/base/browser/ui/dropdown/dropdown.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 5f17647363c..99c11cdd6e2 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -14,6 +14,7 @@ import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses } from 'vs/base/browser/dom'; import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { Emitter } from 'vs/base/common/event'; export interface ILabelRenderer { (container: HTMLElement): IDisposable | null; @@ -29,7 +30,10 @@ export class BaseDropdown extends ActionRunner { private boxContainer?: HTMLElement; private _label?: HTMLElement; private contents?: HTMLElement; + private visible: boolean | undefined; + private _onDidChangeVisibility = new Emitter(); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; constructor(container: HTMLElement, options: IBaseDropdownOptions) { super(); @@ -101,11 +105,17 @@ export class BaseDropdown extends ActionRunner { } show(): void { - this.visible = true; + if (!this.visible) { + this.visible = true; + this._onDidChangeVisibility.fire(true); + } } hide(): void { - this.visible = false; + if (this.visible) { + this.visible = false; + this._onDidChangeVisibility.fire(false); + } } isVisible(): boolean { @@ -303,6 +313,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.element.tabIndex = 0; this.element.setAttribute('role', 'button'); this.element.setAttribute('aria-haspopup', 'true'); + this.element.setAttribute('aria-expanded', 'false'); this.element.title = this._action.label || ''; return null; @@ -321,6 +332,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { } this.dropdownMenu = this._register(new DropdownMenu(container, options)); + this._register(this.dropdownMenu.onDidChangeVisibility(visible => this.element?.setAttribute('aria-expanded', `${visible}`))); this.dropdownMenu.menuOptions = { actionViewItemProvider: this.actionViewItemProvider, From 4c1fb1df7aa103e0678a226c2170873102cd0084 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 8 May 2020 12:07:59 +0200 Subject: [PATCH 040/148] fixes #97110 --- src/vs/workbench/contrib/debug/browser/debugSession.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 693874d0d8f..325f1e7f4f7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -735,6 +735,7 @@ export class DebugSession implements IDebugSession { await this.raw.configurationDone(); } catch (e) { // Disconnect the debug session on configuration done error #10596 + this.notificationService.error(e); if (this.raw) { this.raw.disconnect(); } From eb4f4694328e38ad6bad0f7728c956d0cd2b4341 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Fri, 8 May 2020 12:23:44 +0200 Subject: [PATCH 041/148] Make LinkedMap iterator state aware --- src/vs/base/common/map.ts | 82 ++++++++----- src/vs/base/test/common/map.test.ts | 114 +++++++++++++----- .../browser/parts/editor/editorsObserver.ts | 6 +- .../tasks/browser/abstractTaskService.ts | 13 +- 4 files changed, 148 insertions(+), 67 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 5d79ad368a7..8790fee01a2 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -587,11 +587,14 @@ export class LinkedMap { private _tail: Item | undefined; private _size: number; + private _state: number; + constructor() { this._map = new Map>(); this._head = undefined; this._tail = undefined; this._size = 0; + this._state = 0; } clear(): void { @@ -599,6 +602,7 @@ export class LinkedMap { this._head = undefined; this._tail = undefined; this._size = 0; + this._state++; } isEmpty(): boolean { @@ -701,34 +705,18 @@ export class LinkedMap { } } - values(): V[] { - const result: V[] = []; - let current = this._head; - while (current) { - result.push(current.value); - current = current.next; - } - return result; - } - - keys(): K[] { - const result: K[] = []; - let current = this._head; - while (current) { - result.push(current.key); - current = current.next; - } - return result; - } - - /* VS Code / Monaco editor runs on es5 which has no Symbol.iterator keys(): IterableIterator { - const current = this._head; + const map = this; + const state = this._state; + let current = this._head; const iterator: IterableIterator = { [Symbol.iterator]() { return iterator; }, - next():IteratorResult { + next(): IteratorResult { + if (map._state !== state) { + throw new Error(`Map got modified during iteration.`); + } if (current) { const result = { value: current.key, done: false }; current = current.next; @@ -742,12 +730,17 @@ export class LinkedMap { } values(): IterableIterator { - const current = this._head; + const map = this; + const state = this._state; + let current = this._head; const iterator: IterableIterator = { [Symbol.iterator]() { return iterator; }, - next():IteratorResult { + next(): IteratorResult { + if (map._state !== state) { + throw new Error(`Map got modified during iteration.`); + } if (current) { const result = { value: current.value, done: false }; current = current.next; @@ -759,7 +752,34 @@ export class LinkedMap { }; return iterator; } - */ + + entries(): IterableIterator<[K, V]> { + const map = this; + const state = this._state; + let current = this._head; + const iterator: IterableIterator<[K, V]> = { + [Symbol.iterator]() { + return iterator; + }, + next(): IteratorResult<[K, V]> { + if (map._state !== state) { + throw new Error(`Map got modified during iteration.`); + } + if (current) { + const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false }; + current = current.next; + return result; + } else { + return { value: undefined, done: true }; + } + } + }; + return iterator; + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this.entries(); + } protected trimOld(newSize: number) { if (newSize >= this.size) { @@ -781,6 +801,7 @@ export class LinkedMap { if (current) { current.previous = undefined; } + this._state++; } private addItemFirst(item: Item): void { @@ -794,6 +815,7 @@ export class LinkedMap { this._head.previous = item; } this._head = item; + this._state++; } private addItemLast(item: Item): void { @@ -807,6 +829,7 @@ export class LinkedMap { this._tail.next = item; } this._tail = item; + this._state++; } private removeItem(item: Item): void { @@ -843,6 +866,7 @@ export class LinkedMap { } item.next = undefined; item.previous = undefined; + this._state++; } private touch(item: Item, touch: Touch): void { @@ -879,6 +903,7 @@ export class LinkedMap { item.next = this._head; this._head.previous = item; this._head = item; + this._state++; } else if (touch === Touch.AsNew) { if (item === this._tail) { return; @@ -902,6 +927,7 @@ export class LinkedMap { item.previous = this._tail; this._tail.next = item; this._tail = item; + this._state++; } } @@ -953,8 +979,8 @@ export class LRUCache extends LinkedMap { this.checkTrim(); } - get(key: K): V | undefined { - return super.get(key, Touch.AsNew); + get(key: K, touch: Touch = Touch.AsNew): V | undefined { + return super.get(key, touch); } peek(key: K): V | undefined { diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index c9a99858854..7d7f768e3bc 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -13,8 +13,8 @@ suite('Map', () => { let map = new LinkedMap(); map.set('ak', 'av'); map.set('bk', 'bv'); - assert.deepStrictEqual(map.keys(), ['ak', 'bk']); - assert.deepStrictEqual(map.values(), ['av', 'bv']); + assert.deepStrictEqual([...map.keys()], ['ak', 'bk']); + assert.deepStrictEqual([...map.values()], ['av', 'bv']); assert.equal(map.first, 'av'); assert.equal(map.last, 'bv'); }); @@ -23,16 +23,16 @@ suite('Map', () => { let map = new LinkedMap(); map.set('ak', 'av'); map.set('ak', 'av', Touch.AsOld); - assert.deepStrictEqual(map.keys(), ['ak']); - assert.deepStrictEqual(map.values(), ['av']); + assert.deepStrictEqual([...map.keys()], ['ak']); + assert.deepStrictEqual([...map.values()], ['av']); }); test('LinkedMap - Touch New one', () => { let map = new LinkedMap(); map.set('ak', 'av'); map.set('ak', 'av', Touch.AsNew); - assert.deepStrictEqual(map.keys(), ['ak']); - assert.deepStrictEqual(map.values(), ['av']); + assert.deepStrictEqual([...map.keys()], ['ak']); + assert.deepStrictEqual([...map.values()], ['av']); }); test('LinkedMap - Touch Old two', () => { @@ -40,8 +40,8 @@ suite('Map', () => { map.set('ak', 'av'); map.set('bk', 'bv'); map.set('bk', 'bv', Touch.AsOld); - assert.deepStrictEqual(map.keys(), ['bk', 'ak']); - assert.deepStrictEqual(map.values(), ['bv', 'av']); + assert.deepStrictEqual([...map.keys()], ['bk', 'ak']); + assert.deepStrictEqual([...map.values()], ['bv', 'av']); }); test('LinkedMap - Touch New two', () => { @@ -49,8 +49,8 @@ suite('Map', () => { map.set('ak', 'av'); map.set('bk', 'bv'); map.set('ak', 'av', Touch.AsNew); - assert.deepStrictEqual(map.keys(), ['bk', 'ak']); - assert.deepStrictEqual(map.values(), ['bv', 'av']); + assert.deepStrictEqual([...map.keys()], ['bk', 'ak']); + assert.deepStrictEqual([...map.values()], ['bv', 'av']); }); test('LinkedMap - Touch Old from middle', () => { @@ -59,8 +59,8 @@ suite('Map', () => { map.set('bk', 'bv'); map.set('ck', 'cv'); map.set('bk', 'bv', Touch.AsOld); - assert.deepStrictEqual(map.keys(), ['bk', 'ak', 'ck']); - assert.deepStrictEqual(map.values(), ['bv', 'av', 'cv']); + assert.deepStrictEqual([...map.keys()], ['bk', 'ak', 'ck']); + assert.deepStrictEqual([...map.values()], ['bv', 'av', 'cv']); }); test('LinkedMap - Touch New from middle', () => { @@ -69,8 +69,8 @@ suite('Map', () => { map.set('bk', 'bv'); map.set('ck', 'cv'); map.set('bk', 'bv', Touch.AsNew); - assert.deepStrictEqual(map.keys(), ['ak', 'ck', 'bk']); - assert.deepStrictEqual(map.values(), ['av', 'cv', 'bv']); + assert.deepStrictEqual([...map.keys()], ['ak', 'ck', 'bk']); + assert.deepStrictEqual([...map.values()], ['av', 'cv', 'bv']); }); test('LinkedMap - basics', function () { @@ -129,6 +129,61 @@ suite('Map', () => { assert.ok(!map.has('1')); }); + test('LinkedMap - Iterators', () => { + const map = new LinkedMap(); + map.set(1, 1); + map.set(2, 2); + map.set(3, 3); + + for (const elem of map.keys()) { + assert.ok(elem); + } + + for (const elem of map.values()) { + assert.ok(elem); + } + + for (const elem of map.entries()) { + assert.ok(elem); + } + + { + const keys = map.keys(); + const values = map.values(); + const entries = map.entries(); + map.get(1); + keys.next(); + values.next(); + entries.next(); + } + + { + const keys = map.keys(); + const values = map.values(); + const entries = map.entries(); + map.get(1, Touch.AsNew); + + let exceptions: number = 0; + try { + keys.next(); + } catch (err) { + exceptions++; + } + try { + values.next(); + } catch (err) { + exceptions++; + } + try { + entries.next(); + } catch (err) { + exceptions++; + } + + assert.strictEqual(exceptions, 3); + } + }); + test('LinkedMap - LRU Cache simple', () => { const cache = new LRUCache(5); @@ -136,10 +191,10 @@ suite('Map', () => { assert.strictEqual(cache.size, 5); cache.set(6, 6); assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [2, 3, 4, 5, 6]); + assert.deepStrictEqual([...cache.keys()], [2, 3, 4, 5, 6]); cache.set(7, 7); assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [3, 4, 5, 6, 7]); + assert.deepStrictEqual([...cache.keys()], [3, 4, 5, 6, 7]); let values: number[] = []; [3, 4, 5, 6, 7].forEach(key => values.push(cache.get(key)!)); assert.deepStrictEqual(values, [3, 4, 5, 6, 7]); @@ -150,11 +205,11 @@ suite('Map', () => { [1, 2, 3, 4, 5].forEach(value => cache.set(value, value)); assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [1, 2, 3, 4, 5]); + assert.deepStrictEqual([...cache.keys()], [1, 2, 3, 4, 5]); cache.get(3); - assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]); + assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]); cache.peek(4); - assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]); + assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]); let values: number[] = []; [1, 2, 3, 4, 5].forEach(key => values.push(cache.get(key)!)); assert.deepStrictEqual(values, [1, 2, 3, 4, 5]); @@ -169,7 +224,7 @@ suite('Map', () => { assert.strictEqual(cache.size, 10); cache.limit = 5; assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [6, 7, 8, 9, 10]); + assert.deepStrictEqual([...cache.keys()], [6, 7, 8, 9, 10]); cache.limit = 20; assert.strictEqual(cache.size, 5); for (let i = 11; i <= 20; i++) { @@ -181,7 +236,7 @@ suite('Map', () => { values.push(cache.get(i)!); assert.strictEqual(cache.get(i), i); } - assert.deepStrictEqual(cache.values(), values); + assert.deepStrictEqual([...cache.values()], values); }); test('LinkedMap - LRU Cache limit with ratio', () => { @@ -193,11 +248,11 @@ suite('Map', () => { assert.strictEqual(cache.size, 10); cache.set(11, 11); assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [7, 8, 9, 10, 11]); + assert.deepStrictEqual([...cache.keys()], [7, 8, 9, 10, 11]); let values: number[] = []; - cache.keys().forEach(key => values.push(cache.get(key)!)); + [...cache.keys()].forEach(key => values.push(cache.get(key)!)); assert.deepStrictEqual(values, [7, 8, 9, 10, 11]); - assert.deepStrictEqual(cache.values(), values); + assert.deepStrictEqual([...cache.values()], values); }); test('LinkedMap - toJSON / fromJSON', () => { @@ -222,7 +277,6 @@ suite('Map', () => { assert.equal(key, 'ck'); assert.equal(value, 'cv'); } - i++; }); }); @@ -237,7 +291,7 @@ suite('Map', () => { map.delete('1'); assert.equal(map.get('1'), undefined); assert.equal(map.size, 0); - assert.equal(map.keys().length, 0); + assert.equal([...map.keys()].length, 0); }); test('LinkedMap - delete Head', function () { @@ -251,8 +305,8 @@ suite('Map', () => { map.delete('1'); assert.equal(map.get('2'), 2); assert.equal(map.size, 1); - assert.equal(map.keys().length, 1); - assert.equal(map.keys()[0], 2); + assert.equal([...map.keys()].length, 1); + assert.equal([...map.keys()][0], 2); }); test('LinkedMap - delete Tail', function () { @@ -266,8 +320,8 @@ suite('Map', () => { map.delete('2'); assert.equal(map.get('1'), 1); assert.equal(map.size, 1); - assert.equal(map.keys().length, 1); - assert.equal(map.keys()[0], 1); + assert.equal([...map.keys()].length, 1); + assert.equal([...map.keys()][0], 1); }); diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index e67d77a552c..4573dbdeb1a 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -48,7 +48,7 @@ export class EditorsObserver extends Disposable { } get editors(): IEditorIdentifier[] { - return this.mostRecentEditorsMap.values(); + return [...this.mostRecentEditorsMap.values()]; } hasEditor(resource: URI): boolean { @@ -283,7 +283,7 @@ export class EditorsObserver extends Disposable { // Across all editor groups else { - await this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude); + await this.doEnsureOpenedEditorsLimit(limit, [...this.mostRecentEditorsMap.values()], exclude); } } @@ -346,7 +346,7 @@ export class EditorsObserver extends Disposable { private serialize(): ISerializedEditorsList { const registry = Registry.as(Extensions.EditorInputFactories); - const entries = this.mostRecentEditorsMap.values(); + const entries = [...this.mostRecentEditorsMap.values()]; const mapGroupToSerializableEditorsOfGroup = new Map(); return { diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index de5fcf72cb2..5cf2c6bf7e5 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -19,7 +19,7 @@ import * as strings from 'vs/base/common/strings'; import { ValidationStatus, ValidationState } from 'vs/base/common/parsers'; import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; -import { LRUCache } from 'vs/base/common/map'; +import { LRUCache, Touch } from 'vs/base/common/map'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IMarkerService } from 'vs/platform/markers/common/markers'; @@ -715,9 +715,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const folderToTasksMap: Map = new Map(); const recentlyUsedTasks = this.getRecentlyUsedTasks(); const tasks: (Task | ConfiguringTask)[] = []; - for (const key of recentlyUsedTasks.keys()) { + for (const entry of recentlyUsedTasks.entries()) { + const key = entry[0]; + const task = entry[1]; const folder = this.getFolderFromTaskKey(key); - const task = JSON.parse(recentlyUsedTasks.get(key)!); if (folder && !folderToTasksMap.has(folder)) { folderToTasksMap.set(folder, []); } @@ -791,13 +792,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (quickOpenHistoryLimit === 0) { return; } - let keys = this._recentlyUsedTasks.keys(); + let keys = [...this._recentlyUsedTasks.keys()]; if (keys.length > quickOpenHistoryLimit) { keys = keys.slice(0, quickOpenHistoryLimit); } const keyValues: [string, string][] = []; for (const key of keys) { - keyValues.push([key, this._recentlyUsedTasks.get(key)!]); + keyValues.push([key, this._recentlyUsedTasks.get(key, Touch.None)!]); } this.storageService.store(AbstractTaskService.RecentlyUsedTasks_KeyV2, JSON.stringify(keyValues), StorageScope.WORKSPACE); } @@ -2325,7 +2326,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer taskMap[key] = task; } }); - const reversed = recentlyUsedTasks.keys().reverse(); + const reversed = [...recentlyUsedTasks.keys()].reverse(); for (const key in reversed) { let task = taskMap[key]; if (task) { From c77cc7c5ce21af6f91c968053a7ce3f4477c20a4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 8 May 2020 14:01:31 +0200 Subject: [PATCH 042/148] Add missing json parse --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 5cf2c6bf7e5..58b81d426c1 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -717,7 +717,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const tasks: (Task | ConfiguringTask)[] = []; for (const entry of recentlyUsedTasks.entries()) { const key = entry[0]; - const task = entry[1]; + const task = JSON.parse(entry[1]); const folder = this.getFolderFromTaskKey(key); if (folder && !folderToTasksMap.has(folder)) { folderToTasksMap.set(folder, []); From 54808814bac0d6ef1850e2c2bd15443c505ead25 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 May 2020 14:07:39 +0200 Subject: [PATCH 043/148] editors - fix bad padding for pinned tab --- .../browser/parts/editor/media/tabstitlecontrol.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index ce81de97af0..992011d8288 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -290,13 +290,12 @@ padding-right: 5px; /* we need less room when sizing is shrink (unless tab is sticky) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close { display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top) { - padding-right: 0; /* remove extra padding when we are running without close button */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top):not(.sticky) { + padding-right: 0; /* remove extra padding when we are running without close button (unless tab is sticky) */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close { From 4578463f905f58c283d2d85941f493be73c59a1c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 8 May 2020 14:40:50 +0200 Subject: [PATCH 044/148] Do not tree shake interfaces or classes that extend or inherit from symbols defined in the default library --- build/gulpfile.editor.js | 3 ++- build/lib/treeshaking.js | 30 +++++++++++++++++++++++++++++- build/lib/treeshaking.ts | 32 +++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index f6106241ced..6cb65c5a166 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -127,6 +127,7 @@ const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => const compileEditorESMTask = task.define('compile-editor-esm', () => { const KEEP_PREV_ANALYSIS = false; + const FAIL_ON_PURPOSE = false; console.log(`Launching the TS compiler at ${path.join(__dirname, '../out-editor-esm')}...`); let result; if (process.platform === 'win32') { @@ -142,7 +143,7 @@ const compileEditorESMTask = task.define('compile-editor-esm', () => { console.log(result.stdout.toString()); console.log(result.stderr.toString()); - if (result.status !== 0) { + if (FAIL_ON_PURPOSE || result.status !== 0) { console.log(`The TS Compilation failed, preparing analysis folder...`); const destPath = path.join(__dirname, '../../vscode-monaco-editor-esm-analysis'); const keepPrevAnalysis = (KEEP_PREV_ANALYSIS && fs.existsSync(destPath)); diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index cd366b9c524..5b1cf0591ec 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -420,7 +420,7 @@ function markNodes(languageService, options) { // (they can be the declaration of a module import) continue; } - if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration))) { + if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program, checker, declaration)) { enqueue_black(declaration.name); for (let j = 0; j < declaration.members.length; j++) { const member = declaration.members[j]; @@ -614,6 +614,34 @@ function generateResult(languageService, shakeLevel) { } //#endregion //#region Utils +function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program, checker, declaration) { + if (!program.isSourceFileDefaultLibrary(declaration.getSourceFile()) && declaration.heritageClauses) { + for (const heritageClause of declaration.heritageClauses) { + for (const type of heritageClause.types) { + const symbol = findSymbolFromHeritageType(checker, type); + if (symbol) { + const decl = symbol.valueDeclaration || (symbol.declarations && symbol.declarations[0]); + if (decl && program.isSourceFileDefaultLibrary(decl.getSourceFile())) { + return true; + } + } + } + } + } + return false; +} +function findSymbolFromHeritageType(checker, type) { + if (ts.isExpressionWithTypeArguments(type)) { + return findSymbolFromHeritageType(checker, type.expression); + } + if (ts.isIdentifier(type)) { + return getRealNodeSymbol(checker, type)[0]; + } + if (ts.isPropertyAccessExpression(type)) { + return findSymbolFromHeritageType(checker, type.name); + } + return null; +} /** * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index b65475299f5..405336bfcbb 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -536,7 +536,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt continue; } - if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration))) { + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program, checker, declaration)) { enqueue_black(declaration.name!); for (let j = 0; j < declaration.members.length; j++) { @@ -752,6 +752,36 @@ function generateResult(languageService: ts.LanguageService, shakeLevel: ShakeLe //#region Utils +function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program: ts.Program, checker: ts.TypeChecker, declaration: ts.ClassDeclaration | ts.InterfaceDeclaration): boolean { + if (!program.isSourceFileDefaultLibrary(declaration.getSourceFile()) && declaration.heritageClauses) { + for (const heritageClause of declaration.heritageClauses) { + for (const type of heritageClause.types) { + const symbol = findSymbolFromHeritageType(checker, type); + if (symbol) { + const decl = symbol.valueDeclaration || (symbol.declarations && symbol.declarations[0]); + if (decl && program.isSourceFileDefaultLibrary(decl.getSourceFile())) { + return true; + } + } + } + } + } + return false; +} + +function findSymbolFromHeritageType(checker: ts.TypeChecker, type: ts.ExpressionWithTypeArguments | ts.Expression | ts.PrivateIdentifier): ts.Symbol | null { + if (ts.isExpressionWithTypeArguments(type)) { + return findSymbolFromHeritageType(checker, type.expression); + } + if (ts.isIdentifier(type)) { + return getRealNodeSymbol(checker, type)[0]; + } + if (ts.isPropertyAccessExpression(type)) { + return findSymbolFromHeritageType(checker, type.name); + } + return null; +} + /** * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ From 4dfd5e4c33e740834a334fb07f42269cb841853d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 8 May 2020 15:05:37 +0200 Subject: [PATCH 045/148] Fix #97150 --- src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index ab1e8ee0f4e..ba8bcf7c7e7 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -79,7 +79,7 @@ const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolv const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Preferences Sync: Show Keybindings Conflicts") }; const resolveSnippetsConflictsCommand = { id: 'workbench.userData.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Preferences Sync: Show User Snippets Conflicts") }; const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Preferences Sync: Configure...") }; -const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title: localize('show sync log', "Preferences Sync: Show Log") }; +const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title: localize('show sync log title', "Preferences Sync: Show Log") }; const syncNowCommand = { id: 'workbench.userData.actions.syncNow', title: localize('sync now', "Preferences Sync: Sync Now"), From 0e0516431f259a4bfef72aceaf4856d62dce8c9d Mon Sep 17 00:00:00 2001 From: Robert Rossmann Date: Fri, 8 May 2020 15:08:33 +0200 Subject: [PATCH 046/148] fix: use correct codicon names for folding control colour customisation --- src/vs/editor/contrib/folding/folding.ts | 6 +++--- src/vs/editor/contrib/folding/foldingDecorations.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 6d444d41e65..2372e889922 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -15,7 +15,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, registerInstantiatedEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { FoldingModel, setCollapseStateAtLevel, CollapseMemento, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines, setCollapseStateForType, toggleCollapseState, setCollapseStateUp } from 'vs/editor/contrib/folding/foldingModel'; -import { FoldingDecorationProvider } from './foldingDecorations'; +import { FoldingDecorationProvider, foldingCollapsedIcon, foldingExpandedIcon } from './foldingDecorations'; import { FoldingRegions, FoldingRegion } from './foldingRanges'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -909,8 +909,8 @@ registerThemingParticipant((theme, collector) => { const editorFoldColor = theme.getColor(editorFoldForeground); if (editorFoldColor) { collector.addRule(` - .monaco-editor .cldr.codicon-chevron-right, - .monaco-editor .cldr.codicon-chevron-down { + .monaco-editor .cldr${foldingExpandedIcon.cssSelector}, + .monaco-editor .cldr${foldingCollapsedIcon.cssSelector} { color: ${editorFoldColor} !important; } `); diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index 1c7861f8ac3..c34e2c2121c 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -9,8 +9,8 @@ import { IDecorationProvider } from 'vs/editor/contrib/folding/foldingModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Codicon, registerIcon } from 'vs/base/common/codicons'; -const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown); -const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight); +export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown); +export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight); export class FoldingDecorationProvider implements IDecorationProvider { From 8e3ae1b639bd74941f99c152dfbfb8626677a588 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Fri, 8 May 2020 15:10:09 +0200 Subject: [PATCH 047/148] Fixed failing test --- src/vs/base/common/map.ts | 6 +++--- src/vs/workbench/browser/parts/editor/baseEditor.ts | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 8790fee01a2..962df44c0de 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -715,7 +715,7 @@ export class LinkedMap { }, next(): IteratorResult { if (map._state !== state) { - throw new Error(`Map got modified during iteration.`); + throw new Error(`LinkedMap got modified during iteration.`); } if (current) { const result = { value: current.key, done: false }; @@ -739,7 +739,7 @@ export class LinkedMap { }, next(): IteratorResult { if (map._state !== state) { - throw new Error(`Map got modified during iteration.`); + throw new Error(`LinkedMap got modified during iteration.`); } if (current) { const result = { value: current.value, done: false }; @@ -763,7 +763,7 @@ export class LinkedMap { }, next(): IteratorResult<[K, V]> { if (map._state !== state) { - throw new Error(`Map got modified during iteration.`); + throw new Error(`LinkedMap got modified during iteration.`); } if (current) { const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false }; diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index be181439013..5c01ee205f7 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -10,7 +10,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { LRUCache } from 'vs/base/common/map'; +import { LRUCache, Touch } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { isEmptyObject } from 'vs/base/common/types'; @@ -256,7 +256,9 @@ export class EditorMemento implements IEditorMemento { moveEditorState(source: URI, target: URI): void { const cache = this.doLoad(); - const cacheKeys = cache.keys(); + // We need a copy of the keys to not iterate over + // newly inserted elements. + const cacheKeys = [...cache.keys()]; for (const cacheKey of cacheKeys) { const resource = URI.parse(cacheKey); @@ -273,7 +275,8 @@ export class EditorMemento implements IEditorMemento { targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved } - const value = cache.get(cacheKey); + // Don't modify LRU state. + const value = cache.get(cacheKey, Touch.None); if (value) { cache.delete(cacheKey); cache.set(targetResource.toString(), value); From 43b5deb706e405d14d1c57328bda44d374b34697 Mon Sep 17 00:00:00 2001 From: Leila Pearson <873990+leilapearson@users.noreply.github.com> Date: Fri, 8 May 2020 06:20:28 -0700 Subject: [PATCH 048/148] Address PR comments. --- src/vs/base/common/comparers.ts | 46 +++++++++++++++------------------ 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index 4696e91bb54..23ab2129be7 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -58,27 +58,17 @@ export function compareFileNamesNumeric(one: string | null, other: string | null let result; // Check for name differences, comparing numbers numerically instead of alphabetically. - result = collatorNumeric.compare(oneName, otherName); + result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName); if (result !== 0) { return result; } - // Using the numeric option in the collator will make compare(`foo1`, `foo01`) === 0. Sort the shorter name first. - if (oneName.length !== otherName.length) { - return oneName.length < otherName.length ? -1 : 1; - } - // Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically. - result = collatorNumericCaseInsensitive.compare(oneExtension, otherExtension); + result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension); if (result !== 0) { return result; } - // If extensions are numerically equal but not equal in length, sort the shorter extension first. - if (oneExtension.length !== otherExtension.length) { - return oneExtension.length < otherExtension.length ? -1 : 1; - } - // Disambiguate the extension case if needed. if (oneExtension !== otherExtension) { return collatorNumeric.compare(oneExtension, otherExtension); @@ -142,27 +132,17 @@ export function compareFileExtensionsNumeric(one: string | null, other: string | let result; // Check for extension differences, ignoring differences in case and comparing numbers numerically. - result = collatorNumericCaseInsensitive.compare(oneExtension, otherExtension); + result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension); if (result !== 0) { return result; } - // Disambiguate equivalent numbers in extensions. - if (oneExtension.length !== otherExtension.length) { - return oneExtension.length < otherExtension.length ? -1 : 1; - } - // Compare names. - result = collatorNumeric.compare(oneName, otherName); + result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName); if (result !== 0) { return result; } - // Disambiguate equivalent numbers in names. - if (oneName.length !== otherName.length) { - return oneName.length < otherName.length ? -1 : 1; - } - // Disambiguate extension case if needed. if (oneExtension !== otherExtension) { return collatorNumeric.compare(oneExtension, otherExtension); @@ -172,7 +152,7 @@ export function compareFileExtensionsNumeric(one: string | null, other: string | } /** Extracts the name and extension from a full filename, with optional special handling for dotfiles */ -export function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] { +function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] { const match = str ? FileNameMatch.exec(str) as Array : ([] as Array); let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || '']; @@ -186,6 +166,22 @@ export function extractNameAndExtension(str?: string | null, dotfilesAsNames = f return result; } +function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) { + // Check for differences + let result = collator.compare(one, other); + if (result !== 0) { + return result; + } + + // In a numeric comparison, `foo1` and `foo01` will compare as equivalent. + // Disambiguate by sorting the shorter string first. + if (one.length !== other.length) { + return one.length < other.length ? -1 : 1; + } + + return 0; +} + function comparePathComponents(one: string, other: string, caseSensitive = false): number { if (!caseSensitive) { one = one && one.toLowerCase(); From 43ff7befd459b2c24c1bb9e418dcb8fe00d40eb6 Mon Sep 17 00:00:00 2001 From: Christopher Maynard Date: Fri, 8 May 2020 10:23:57 -0400 Subject: [PATCH 049/148] Moved eslint-disable... to correct import line --- .../contrib/notebook/browser/view/renderers/cellRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 deda7daf6db..a3c99526d5f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// eslint-disable-next-line code-import-patterns import { getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; @@ -20,6 +19,7 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc import { deepClone } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { escape } from 'vs/base/common/strings'; +// eslint-disable-next-line code-import-patterns import 'vs/css!vs/workbench/contrib/notebook/browser/media/notebook'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; From 09e7f4e5d33d1952d0947bee80d4b34ef065deef Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 8 May 2020 16:52:13 +0200 Subject: [PATCH 050/148] Preserve focus in status bar fixes #97241 --- src/vs/workbench/browser/layout.ts | 7 +++-- .../browser/parts/statusbar/statusbarPart.ts | 30 ++++++++++++++++--- .../services/statusbar/common/statusbar.ts | 5 ++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index bb0d6570508..e8d196a218b 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -178,6 +178,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private notificationService!: INotificationService; private themeService!: IThemeService; private activityBarService!: IActivityBarService; + private statusBarService!: IStatusbarService; protected readonly state = { fullscreen: false, @@ -262,7 +263,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.titleService = accessor.get(ITitleService); this.notificationService = accessor.get(INotificationService); this.activityBarService = accessor.get(IActivityBarService); - accessor.get(IStatusbarService); // not used, but called to ensure instantiated + this.statusBarService = accessor.get(IStatusbarService); // Listeners this.registerLayoutListeners(); @@ -850,8 +851,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi case Parts.ACTIVITYBAR_PART: this.activityBarService.focusActivityBar(); break; + case Parts.STATUSBAR_PART: + this.statusBarService.focus(); default: - // Status Bar, Activity Bar and Title Bar simply pass focus to container + // Title Bar simply pass focus to container const container = this.getContainer(part); if (container) { container.focus(); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 2b74fcdbd38..fa4de06a425 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -69,6 +69,10 @@ class StatusbarViewModel extends Disposable { get entries(): IStatusbarViewModelEntry[] { return this._entries; } private hidden!: Set; + get lastFocusedEntry(): IStatusbarViewModelEntry | undefined { + return this._lastFocusedEntry && !this.isHidden(this._lastFocusedEntry.id) ? this._lastFocusedEntry : undefined; + } + private _lastFocusedEntry: IStatusbarViewModelEntry | undefined; private readonly _onDidChangeEntryVisibility = this._register(new Emitter<{ id: string, visible: boolean }>()); readonly onDidChangeEntryVisibility = this._onDidChangeEntryVisibility.event; @@ -219,6 +223,7 @@ class StatusbarViewModel extends Disposable { if (focused) { const entry = getVisibleEntry(this._entries.indexOf(focused) + delta); if (entry) { + this._lastFocusedEntry = entry; entry.labelContainer.focus(); return; } @@ -226,6 +231,7 @@ class StatusbarViewModel extends Disposable { const entry = getVisibleEntry(restartPosition); if (entry) { + this._lastFocusedEntry = entry; entry.labelContainer.focus(); } } @@ -493,6 +499,15 @@ export class StatusbarPart extends Part implements IStatusbarService { this.viewModel.focusPreviousEntry(); } + focus(preserveEntryFocus = true): void { + this.getContainer()?.focus(); + const lastFocusedEntry = this.viewModel.lastFocusedEntry; + if (preserveEntryFocus && lastFocusedEntry) { + // Need a timeout, for some reason without it the inner label container will not get focused + setTimeout(() => lastFocusedEntry.labelContainer.focus(), 0); + } + } + createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; @@ -680,10 +695,6 @@ export class StatusbarPart extends Part implements IStatusbarService { return itemContainer; } - focus(): void { - this.getContainer(); - } - layout(width: number, height: number): void { super.layout(width, height); super.layoutContents(width, height); @@ -935,3 +946,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ statusBarService.focusNextEntry(); } }); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.statusBar.clearFocus', + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.Escape, + when: CONTEXT_STATUS_BAR_FOCUSED, + handler: (accessor: ServicesAccessor) => { + const statusBarService = accessor.get(IStatusbarService); + statusBarService.focus(false); + } +}); diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index 7a90dfab5f3..af13535d622 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -90,6 +90,11 @@ export interface IStatusbarService { */ updateEntryVisibility(id: string, visible: boolean): void; + /** + * Focused the status bar. If one of the status bar entries was focused, focuses it directly. + */ + focus(preserveEntryFocus?: boolean): void; + /** * Focuses the next status bar entry. If none focused, focuses the first. */ From 602d2a217c6fd4c25afc14467ead4eae0c7923ce Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 8 May 2020 16:54:28 +0200 Subject: [PATCH 051/148] storage.type.struct is not an appropriate textmate fallback for struct. Fixes #97162 --- src/vs/platform/theme/common/tokenClassificationRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 14a1b52e4c3..754c93d5bd7 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -515,7 +515,7 @@ function createDefaultTokenClassificationRegistry(): TokenClassificationRegistry registerTokenType('namespace', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['support.type']]); - registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']]); + registerTokenType('struct', nls.localize('struct', "Style for structs."), [['entity.name.type.struct']]); registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.type.class'], ['support.class']]); registerTokenType('interface', nls.localize('interface', "Style for interfaces."), [['entity.name.type.interface']]); registerTokenType('enum', nls.localize('enum', "Style for enums."), [['entity.name.type.enum']]); From 863d8b279453d460132d819311acec6d6736e182 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 11:26:57 -0500 Subject: [PATCH 052/148] Upload config task can continue on error --- build/azure-pipelines/darwin/product-build-darwin.yml | 5 +++++ build/azure-pipelines/darwin/publish.sh | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 223fb3377d7..12e82fbac32 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -252,6 +252,11 @@ steps: ./build/azure-pipelines/darwin/publish.sh displayName: Publish +- script: | + yarn gulp upload-vscode-configuration + displayName: Upload configuration (for Bing settings search) + continueOnError: true + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' continueOnError: true diff --git a/build/azure-pipelines/darwin/publish.sh b/build/azure-pipelines/darwin/publish.sh index f0375d2a3c8..fe3e9a59986 100755 --- a/build/azure-pipelines/darwin/publish.sh +++ b/build/azure-pipelines/darwin/publish.sh @@ -22,6 +22,3 @@ node build/azure-pipelines/common/createAsset.js \ # node build/azure-pipelines/common/symbols.js "$VSCODE_MIXIN_PASSWORD" "$VSCODE_HOCKEYAPP_TOKEN" x64 "$VSCODE_HOCKEYAPP_ID_MACOS" # Skip hockey app because build failure. # https://github.com/microsoft/vscode/issues/90491 - -# upload configuration -yarn gulp upload-vscode-configuration From ac5d1a097efd946717c444b6e52c66ad092480f5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 11:39:01 -0500 Subject: [PATCH 053/148] Validate that search folders exist for all providers, not just the disk provider Fix #94170 --- .../services/search/common/searchService.ts | 60 ++++++++++--------- .../services/search/node/searchService.ts | 45 ++++++-------- 2 files changed, 48 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 4c44030ae27..cec7149d6fe 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -116,41 +116,43 @@ export class SearchService extends Disposable implements ISearchService { schemesInQuery.forEach(scheme => providerActivations.push(this.extensionService.activateByEvent(`onSearch:${scheme}`))); providerActivations.push(this.extensionService.activateByEvent('onSearch:file')); - const providerPromise = Promise.all(providerActivations) - .then(() => this.extensionService.whenInstalledExtensionsRegistered()) - .then(() => { - // Cancel faster if search was canceled while waiting for extensions + const providerPromise = (async () => { + await Promise.all(providerActivations); + this.extensionService.whenInstalledExtensionsRegistered(); + + // Cancel faster if search was canceled while waiting for extensions + if (token && token.isCancellationRequested) { + return Promise.reject(canceled()); + } + + const progressCallback = (item: ISearchProgressItem) => { if (token && token.isCancellationRequested) { - return Promise.reject(canceled()); + return; } - const progressCallback = (item: ISearchProgressItem) => { - if (token && token.isCancellationRequested) { - return; - } - - if (onProgress) { - onProgress(item); - } - }; - - return this.searchWithProviders(query, progressCallback, token); - }) - .then(completes => { - completes = arrays.coalesce(completes); - if (!completes.length) { - return { - limitHit: false, - results: [] - }; + if (onProgress) { + onProgress(item); } + }; - return { - limitHit: completes[0] && completes[0].limitHit, - stats: completes[0].stats, - results: arrays.flatten(completes.map((c: ISearchComplete) => c.results)) + const exists = await Promise.all(query.folderQueries.map(query => this.fileService.exists(query.folder))); + query.folderQueries = query.folderQueries.filter((_, i) => exists[i]); + + let completes = await this.searchWithProviders(query, progressCallback, token); + completes = arrays.coalesce(completes); + if (!completes.length) { + return { + limitHit: false, + results: [] }; - }); + } + + return { + limitHit: completes[0] && completes[0].limitHit, + stats: completes[0].stats, + results: arrays.flatten(completes.map((c: ISearchComplete) => c.results)) + }; + })(); return new Promise((resolve, reject) => { if (token) { diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 53cfa0b9f15..a9170133473 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -51,7 +51,6 @@ export class DiskSearch implements ISearchResultProvider { searchDebug: IDebugParams | undefined, @ILogService private readonly logService: ILogService, @IConfigurationService private readonly configService: IConfigurationService, - @IFileService private readonly fileService: IFileService ) { const timeout = this.configService.getValue().search.maintainFileSearchCache ? Number.MAX_VALUE : @@ -91,41 +90,31 @@ export class DiskSearch implements ISearchResultProvider { } textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise { - const folderQueries = query.folderQueries || []; - return Promise.all(folderQueries.map(q => this.fileService.exists(q.folder))) - .then(exists => { - if (token && token.isCancellationRequested) { - throw canceled(); - } + if (token && token.isCancellationRequested) { + throw canceled(); + } - query.folderQueries = folderQueries.filter((q, index) => exists[index]); - const event: Event = this.raw.textSearch(query); + const event: Event = this.raw.textSearch(query); - return DiskSearch.collectResultsFromEvent(event, onProgress, token); - }); + return DiskSearch.collectResultsFromEvent(event, onProgress, token); } fileSearch(query: IFileQuery, token?: CancellationToken): Promise { - const folderQueries = query.folderQueries || []; - return Promise.all(folderQueries.map(q => this.fileService.exists(q.folder))) - .then(exists => { - if (token && token.isCancellationRequested) { - throw canceled(); - } + if (token && token.isCancellationRequested) { + throw canceled(); + } - query.folderQueries = folderQueries.filter((q, index) => exists[index]); - let event: Event; - event = this.raw.fileSearch(query); + let event: Event; + event = this.raw.fileSearch(query); - const onProgress = (p: ISearchProgressItem) => { - if (!isFileMatch(p)) { - // Should only be for logs - this.logService.debug('SearchService#search', p.message); - } - }; + const onProgress = (p: ISearchProgressItem) => { + if (!isFileMatch(p)) { + // Should only be for logs + this.logService.debug('SearchService#search', p.message); + } + }; - return DiskSearch.collectResultsFromEvent(event, onProgress, token); - }); + return DiskSearch.collectResultsFromEvent(event, onProgress, token); } /** From 26ededa858560a7a79c6d22cd970f74291146784 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 8 May 2020 11:55:22 -0700 Subject: [PATCH 054/148] NotebookDocumentEditEvent --- .../vscode-notebook-tests/src/notebookTestMain.ts | 2 +- src/vs/vscode.proposed.d.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/extensions/vscode-notebook-tests/src/notebookTestMain.ts index e5402feb298..4281ba002fd 100644 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookTestMain.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; export function activate(context: vscode.ExtensionContext): any { context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', { - onDidChangeNotebook: new vscode.EventEmitter().event, + onDidChangeNotebook: new vscode.EventEmitter().event, openNotebook: async (_resource: vscode.Uri) => { return { languages: ['typescript'], diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e44809aaa68..ff7bebe43b1 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1682,11 +1682,20 @@ declare module 'vscode' { readonly metadata: NotebookDocumentMetadata; } + interface NotebookDocumentEditEvent { + + /** + * The document that the edit is for. + */ + readonly document: NotebookDocument; + } + export interface NotebookContentProvider { openNotebook(uri: Uri): NotebookData | Promise; saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; - readonly onDidChangeNotebook: Event; + readonly onDidChangeNotebook: Event; + // revert?(document: NotebookDocument, cancellation: CancellationToken): Thenable; // backup?(document: NotebookDocument, cancellation: CancellationToken): Thenable; From d996bb1d285c80dbf262526553d90e414243d63b Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 8 May 2020 12:21:03 -0700 Subject: [PATCH 055/148] fix: entitlement for default helper and browser process (#97263) Since we load libffmeg.dylib in the browser process which needs to skip library validation and also we run v8 in the browser process that requires the JIT permission. --- build/azure-pipelines/darwin/entitlements.plist | 8 ++++++++ build/azure-pipelines/darwin/helper-entitlements.plist | 8 ++++++++ build/azure-pipelines/darwin/product-build-darwin.yml | 1 + 3 files changed, 17 insertions(+) create mode 100644 build/azure-pipelines/darwin/helper-entitlements.plist diff --git a/build/azure-pipelines/darwin/entitlements.plist b/build/azure-pipelines/darwin/entitlements.plist index 6631ffa6f24..be8b7163da7 100644 --- a/build/azure-pipelines/darwin/entitlements.plist +++ b/build/azure-pipelines/darwin/entitlements.plist @@ -2,5 +2,13 @@ + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.allow-dyld-environment-variables + diff --git a/build/azure-pipelines/darwin/helper-entitlements.plist b/build/azure-pipelines/darwin/helper-entitlements.plist new file mode 100644 index 00000000000..123d12a53e9 --- /dev/null +++ b/build/azure-pipelines/darwin/helper-entitlements.plist @@ -0,0 +1,8 @@ + + + + + com.apple.security.cs.disable-library-validation + + + diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 12e82fbac32..95d3d8ebca2 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -173,6 +173,7 @@ steps: security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain codesign -s 99FM488X57 --deep --force --options runtime --entitlements build/azure-pipelines/darwin/entitlements.plist "$APP_ROOT"/*.app + codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper.app" codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-gpu-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (GPU).app" codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-plugin-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (Plugin).app" codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-renderer-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (Renderer).app" From bbdd7e777b0337951fd74c06002dbc69355eaa36 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 8 May 2020 13:19:53 -0700 Subject: [PATCH 056/148] Don't return an override if there are no custom editors Fixes #97271 --- .../workbench/contrib/customEditor/browser/customEditors.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 97b9c5bfaaf..95fecef9b18 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -431,6 +431,10 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo const currentEditor = group?.editors.find(editor => isEqual(editor.resource, resource)); const customEditors = this.customEditorService.getAllCustomEditors(resource); + if (!customEditors.length) { + return []; + } + return [ { ...defaultEditorOverrideEntry, From 36b1cb2117dd7f038f68f0b288851988b65a6d41 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Fri, 8 May 2020 13:32:26 -0700 Subject: [PATCH 057/148] Fix #97299 --- src/vs/editor/contrib/find/findWidget.css | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 4882113bec5..3805801379b 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -126,10 +126,6 @@ justify-content: center; } -.monaco-editor .find-widget .button:not(.disabled):hover { - background-color: rgba(0, 0, 0, 0.1); -} - .monaco-editor .find-widget .button.left { margin-left: 0; margin-right: 3px; @@ -211,11 +207,6 @@ margin-left: -4px; } -.monaco-editor.hc-black .find-widget .button:not(.disabled):hover, -.monaco-editor.vs-dark .find-widget .button:not(.disabled):hover { - background-color: rgba(255, 255, 255, 0.1); -} - .monaco-editor.hc-black .find-widget .button:before { position: relative; top: 1px; From 5d8cb8a6a30e2123d0dca80b2cf4e4bbd7d1518b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 08:54:37 -0500 Subject: [PATCH 058/148] Fix #97192 - delete bad setting validation --- .../services/preferences/common/preferencesValidation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/services/preferences/common/preferencesValidation.ts b/src/vs/workbench/services/preferences/common/preferencesValidation.ts index 9a1080649a6..56edf974a3b 100644 --- a/src/vs/workbench/services/preferences/common/preferencesValidation.ts +++ b/src/vs/workbench/services/preferences/common/preferencesValidation.ts @@ -69,7 +69,6 @@ export function getInvalidTypeError(value: any, type: undefined | string | strin (valueType === 'boolean' && !canBeType(typeArr, 'boolean')) || (valueType === 'object' && !canBeType(typeArr, 'object', 'null', 'array')) || (valueType === 'string' && !canBeType(typeArr, 'string', 'number', 'integer')) || - (typeof parseFloat(value) === 'number' && !isNaN(parseFloat(value)) && !canBeType(typeArr, 'number', 'integer')) || (Array.isArray(value) && !canBeType(typeArr, 'array')) ) { if (typeof type !== 'undefined') { From 08a17329a61e76baea5311ab35871848e4de6a22 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 16:21:01 -0500 Subject: [PATCH 059/148] Fix notebook preserving focus in first cell --- src/vs/workbench/contrib/notebook/browser/notebookEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 929dbc680d9..d6826fa8be0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -616,7 +616,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { state.cellTotalHeights = cellHeights; const focus = this.list.getFocus()[0]; - if (focus) { + if (typeof focus === 'number') { const element = this.notebookViewModel!.viewCells[focus]; const itemDOM = this.list?.domElementOfElement(element!); let editorFocused = false; From 4b132c332e20939dbfd97045694d9fe8262cbf1f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 16:22:16 -0500 Subject: [PATCH 060/148] Tweaks from notebook focus PR --- .../notebook/browser/contrib/coreActions.ts | 43 ++++++++-- .../view/renderers/backLayerWebView.ts | 78 ++++++++----------- 2 files changed, 70 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 0824f7f02e0..c4620b3a197 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -66,7 +66,8 @@ const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; const CHANGE_CELL_LANGUAGE = 'notebook.cell.changeLanguage'; -const FOCUS_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutput'; +const FOCUS_IN_OUTPUT_COMMAND_ID = 'notebook.cell.focusInOutput'; +const FOCUS_OUT_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutOutput'; export const NOTEBOOK_ACTIONS_CATEGORY = localize('notebookActions.category', "Notebook"); @@ -1126,14 +1127,16 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: FOCUS_OUTPUT_COMMAND_ID, - title: localize('focusOutput', 'Focus output'), + id: FOCUS_IN_OUTPUT_COMMAND_ID, + title: localize('focusOutput', 'Focus In Active Cell Output'), category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - weight: EDITOR_WIDGET_ACTION_WEIGHT - } + mac: { primary: KeyMod.WinCtrl | KeyCode.DownArrow, }, + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true }); } @@ -1151,6 +1154,36 @@ registerAction2(class extends Action2 { } }); +registerAction2(class extends Action2 { + constructor() { + super({ + id: FOCUS_OUT_OUTPUT_COMMAND_ID, + title: localize('focusOutputOut', 'Focus Out Active Cell Output'), + category: NOTEBOOK_ACTIONS_CATEGORY, + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + mac: { primary: KeyMod.WinCtrl | KeyCode.UpArrow, }, + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(context)) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + const editor = context.notebookEditor; + const activeCell = context.cell; + editor.focusNotebookCell(activeCell, 'editor'); + } +}); + registerAction2(class extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index c079e42e9a9..574e627b697 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -105,6 +105,14 @@ export interface IUpdatePreloadResourceMessage { resources: string[]; } +function html(strings: TemplateStringsArray, ...values: any[]): string { + let str = ''; + strings.forEach((string, i) => { + str += string + values[i]; + }); + return str; +} + type IMessage = IDimensionMessage | IScrollAckMessage | IWheelMessage | IMouseEnterMessage | IMouseLeaveMessage | IBlurOutputMessage; let version = 0; @@ -120,8 +128,6 @@ export class BackLayerWebView extends Disposable { private readonly _onMessage = this._register(new Emitter()); public readonly onMessage: Event = this._onMessage.event; private _initalized: Promise; - private activeCellId: string | undefined; - constructor( public notebookEditor: INotebookEditor, @@ -174,7 +180,7 @@ ${loaderJs} } generateContent(outputNodePadding: number, coreDependencies: string) { - return /* html */` + return html` @@ -290,6 +296,21 @@ ${loaderJs} }); }; + function createFocusSink(cellId, focusNext) { + const element = document.createElement('div'); + element.tabIndex = 0; + element.addEventListener('focus', () => { + vscode.postMessage({ + __vscode_notebook_message: true, + type: 'focus-editor', + id: cellId, + focusNext + }); + }); + + return element; + } + window.addEventListener('wheel', handleWheel); window.addEventListener('message', event => { @@ -303,16 +324,8 @@ ${loaderJs} if (!cellOutputContainer) { const container = document.getElementById('container'); - let upperWrapperElement = document.createElement('div'); - upperWrapperElement.tabIndex = 0; + const upperWrapperElement = createFocusSink(outputId); container.appendChild(upperWrapperElement); - upperWrapperElement.addEventListener('focus', () => { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'focus-editor', - id: outputId, - }); - }); let newElement = document.createElement('div'); @@ -337,31 +350,8 @@ ${loaderJs} }); }); - const handleKeyDown = (event) => { - if (event.defaultPrevented || !(event.key === 'ArrowUp' && event.ctrlKey)) { - return; - } - - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'focus-editor', - id: outputId, - }); - }; - - cellOutputContainer.addEventListener("keydown", handleKeyDown); - - let lowerWrapperElement = document.createElement('div'); - lowerWrapperElement.tabIndex = 0; + const lowerWrapperElement = createFocusSink(outputId, true); container.appendChild(lowerWrapperElement); - lowerWrapperElement.addEventListener('focus', () => { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'focus-editor', - id: outputId, - focusNext: true - }); - }); } let outputNode = document.createElement('div'); @@ -466,15 +456,6 @@ ${loaderJs} initialize(content: string) { this.webview = this._createInset(this.webviewService, content); this.webview.mountTo(this.element); - this.webview.onDidFocus(() => { - if (this.activeCellId) { - this.webview.sendMessage({ - type: 'focus-output', - id: this.activeCellId - }); - this.activeCellId = undefined; - } - }); this._register(this.webview.onDidClickLink(link => { this.openerService.open(link, { fromUserGesture: true }); @@ -679,8 +660,13 @@ ${loaderJs} } focusOutput(cellId: string) { - this.activeCellId = cellId; this.webview.focus(); + setTimeout(() => { // Need this, or focus decoration is not shown. No clue. + this.webview.sendMessage({ + type: 'focus-output', + id: cellId + }); + }, 50); } updateRendererPreloads(preloads: Set) { From 11f2cb7779a6ad56d7dc91e06747da6f3d4820a2 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 16:58:32 -0500 Subject: [PATCH 061/148] Don't show validations for invalid setting defaults Fix #97034 --- src/vs/workbench/contrib/preferences/browser/settingsTree.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 21738f1e575..495a328ae6f 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -740,7 +740,7 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I } private renderValidations(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate) { - const errMsg = getInvalidTypeError(dataElement.value, dataElement.setting.type); + const errMsg = dataElement.isConfigured && getInvalidTypeError(dataElement.value, dataElement.setting.type); if (errMsg) { DOM.addClass(template.containerElement, 'invalid-input'); template.validationErrorMessageElement.innerText = errMsg; @@ -1486,7 +1486,7 @@ class SettingsTreeDelegate extends CachedListVirtualDelegate Date: Fri, 8 May 2020 15:19:37 -0700 Subject: [PATCH 062/148] notebooks: rerender output if the webview reloads (#97282) --- .../view/renderers/backLayerWebView.ts | 29 +++++++++++++++---- .../webview/browser/baseWebviewElement.ts | 8 +++++ .../browser/dynamicWebviewEditorOverlay.ts | 4 +++ .../contrib/webview/browser/pre/main.js | 2 ++ .../contrib/webview/browser/webview.ts | 1 + .../electron-browser/pre/electron-index.js | 2 +- 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 574e627b697..962168f7de1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -77,6 +77,7 @@ export interface ICreationRequestMessage { outputId: string; top: number; left: number; + initiallyHidden?: boolean; } export interface IContentWidgetTopRequest { @@ -105,6 +106,13 @@ export interface IUpdatePreloadResourceMessage { resources: string[]; } +interface ICachedInset { + outputId: string; + cell: CodeCellViewModel; + preloads: ReadonlySet; + cachedCreation: ICreationRequestMessage; +} + function html(strings: TemplateStringsArray, ...values: any[]): string { let str = ''; strings.forEach((string, i) => { @@ -119,7 +127,7 @@ let version = 0; export class BackLayerWebView extends Disposable { element: HTMLElement; webview!: WebviewElement; - insetMapping: Map = new Map(); + insetMapping: Map = new Map(); hiddenInsetMapping: Set = new Set(); reversedInsetMapping: Map = new Map(); preloadsCache: Map = new Map(); @@ -378,6 +386,9 @@ ${loaderJs} height: outputNode.clientHeight } }); + + // don't hide until after this step so that the height is right + cellOutputContainer.style.display = event.data.initiallyHidden ? 'none' : 'block'; } break; case 'view-scroll': @@ -461,6 +472,14 @@ ${loaderJs} this.openerService.open(link, { fromUserGesture: true }); })); + this._register(this.webview.onDidReload(() => { + this.preloadsCache.clear(); + for (const [output, inset] of this.insetMapping.entries()) { + this.updateRendererPreloads(inset.preloads); + this.webview.sendMessage({ ...inset.cachedCreation, initiallyHidden: this.hiddenInsetMapping.has(output) }); + } + })); + this._register(this.webview.onMessage((data: IMessage) => { if (data.__vscode_notebook_message) { if (data.type === 'dimension') { @@ -550,7 +569,7 @@ ${loaderJs} return true; } - if (outputOffset === outputCache.cacheOffset) { + if (outputOffset === outputCache.cachedCreation.top) { return false; } @@ -564,7 +583,7 @@ ${loaderJs} let outputIndex = item.cell.outputs.indexOf(item.output); let outputOffset = item.cellTop + item.cell.getOutputOffset(outputIndex); - outputCache.cacheOffset = outputOffset; + outputCache.cachedCreation.top = outputOffset; this.hiddenInsetMapping.delete(item.output); return { @@ -614,7 +633,7 @@ ${loaderJs} }; this.webview.sendMessage(message); - this.insetMapping.set(output, { outputId: outputId, cell: cell, cacheOffset: initialTop }); + this.insetMapping.set(output, { outputId: outputId, cell: cell, preloads, cachedCreation: message }); this.hiddenInsetMapping.delete(output); this.reversedInsetMapping.set(outputId, output); } @@ -669,7 +688,7 @@ ${loaderJs} }, 50); } - updateRendererPreloads(preloads: Set) { + updateRendererPreloads(preloads: ReadonlySet) { let resources: string[] = []; let extensionLocations: URI[] = []; preloads.forEach(preload => { diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 4d37537fa17..bfe8903d4d6 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -21,6 +21,7 @@ export const enum WebviewMessageChannels { didScroll = 'did-scroll', didFocus = 'did-focus', didBlur = 'did-blur', + didLoad = 'did-load', doUpdateState = 'do-update-state', doReload = 'do-reload', loadResource = 'load-resource', @@ -153,6 +154,9 @@ export abstract class BaseWebview extends Disposable { private readonly _onDidClickLink = this._register(new Emitter()); public readonly onDidClickLink = this._onDidClickLink.event; + private readonly _onDidReload = this._register(new Emitter()); + public readonly onDidReload = this._onDidReload.event; + private readonly _onMessage = this._register(new Emitter()); public readonly onMessage = this._onMessage.event; @@ -216,6 +220,10 @@ export abstract class BaseWebview extends Disposable { public reload(): void { this.doUpdateContent(); + const subscription = this._register(this.on(WebviewMessageChannels.didLoad, () => { + this._onDidReload.fire(); + subscription.dispose(); + })); } public set html(value: string) { diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index c40464378d4..2ce69211f98 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -130,6 +130,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv this._webviewEvents.add(webview.onMessage(x => { this._onMessage.fire(x); })); this._webviewEvents.add(webview.onMissingCsp(x => { this._onMissingCsp.fire(x); })); this._webviewEvents.add(webview.onDidWheel(x => { this._onDidWheel.fire(x); })); + this._webviewEvents.add(webview.onDidReload(() => { this._onDidReload.fire(); })); this._webviewEvents.add(webview.onDidScroll(x => { this._initialScrollProgress = x.scrollYPercentage; @@ -189,6 +190,9 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv private readonly _onDidClickLink = this._register(new Emitter()); public readonly onDidClickLink: Event = this._onDidClickLink.event; + private readonly _onDidReload = this._register(new Emitter()); + public readonly onDidReload = this._onDidReload.event; + private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number; }>()); public readonly onDidScroll: Event<{ scrollYPercentage: number; }> = this._onDidScroll.event; diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index f2ca5011ddc..8fc84cd45df 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -517,6 +517,8 @@ }); pendingMessages = []; } + + host.postMessage('did-load'); }; /** diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index e592683896b..610f20272be 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -84,6 +84,7 @@ export interface Webview extends IDisposable { readonly onDidScroll: Event<{ scrollYPercentage: number }>; readonly onDidWheel: Event; readonly onDidUpdateState: Event; + readonly onDidReload: Event; readonly onMessage: Event; readonly onMissingCsp: Event; diff --git a/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js b/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js index 8be7c9520a6..e87ab16dba4 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js +++ b/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js @@ -79,4 +79,4 @@ }); require('../../browser/pre/main')(host); -}()); \ No newline at end of file +}()); From a2fa9497d02b3530ca7e7ffbb6e1ba6f8bed1874 Mon Sep 17 00:00:00 2001 From: petevdp Date: Fri, 8 May 2020 15:25:12 -0700 Subject: [PATCH 063/148] allow the inline diff editor to be centered --- src/vs/workbench/browser/layout.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index e8d196a218b..ac32addcfee 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -44,6 +44,7 @@ import { LineNumbersType } from 'vs/editor/common/config/editorOptions'; import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { URI } from 'vs/base/common/uri'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; export enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', @@ -398,6 +399,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const newMenubarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService); this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); + // Centered Layout + this.centerEditorLayout(this.state.editor.centered, skipLayout); } private setSideBarPosition(position: Position): void { @@ -1208,9 +1211,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi let smartActive = active; const activeEditor = this.editorService.activeEditor; - if (this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize') - && (this.editorGroupService.groups.length > 1 || (activeEditor && activeEditor instanceof SideBySideEditorInput))) { - smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group. + + const isSideBySideLayout = activeEditor + && activeEditor instanceof SideBySideEditorInput + // DiffEditorInput inherits from SideBySideEditorInput but can still be functionally an inline editor. + && (!(activeEditor instanceof DiffEditorInput) || this.configurationService.getValue('diffEditor.renderSideBySide')); + + const isCenteredLayoutAutoResizing = this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize'); + if ( + isCenteredLayoutAutoResizing + && (this.editorGroupService.groups.length > 1 || isSideBySideLayout) + ) { + smartActive = false; } // Enter Centered Editor Layout From b82e5905ef2ae58d709abe2c7d58a057230940fd Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 18:20:34 -0500 Subject: [PATCH 064/148] Don't show validations for default setting values in settings editor Fix #97034 --- .../common/preferencesValidation.ts | 43 ++++++++++++------- ....test.ts => preferencesValidation.test.ts} | 43 ++++++++++++++++++- 2 files changed, 68 insertions(+), 18 deletions(-) rename src/vs/workbench/services/preferences/test/common/{preferencesModel.test.ts => preferencesValidation.test.ts} (86%) diff --git a/src/vs/workbench/services/preferences/common/preferencesValidation.ts b/src/vs/workbench/services/preferences/common/preferencesValidation.ts index 56edf974a3b..6a205333596 100644 --- a/src/vs/workbench/services/preferences/common/preferencesValidation.ts +++ b/src/vs/workbench/services/preferences/common/preferencesValidation.ts @@ -55,30 +55,41 @@ export function createValidator(prop: IConfigurationPropertySchema): (value: any }; } +/** + * Returns an error string if the value is invalid and can't be displayed in the settings UI for the given type. + */ export function getInvalidTypeError(value: any, type: undefined | string | string[]): string | undefined { - let typeArr = Array.isArray(type) ? type : [type]; - const isNullable = canBeType(typeArr, 'null'); - if (canBeType(typeArr, 'number', 'integer') && (typeArr.length === 1 || typeArr.length === 2 && isNullable)) { - if (value === '' || isNaN(+value)) { - return nls.localize('validations.expectedNumeric', "Value must be a number."); - } + if (typeof type === 'undefined') { + return; } - const valueType = typeof value; - if ( - (valueType === 'boolean' && !canBeType(typeArr, 'boolean')) || - (valueType === 'object' && !canBeType(typeArr, 'object', 'null', 'array')) || - (valueType === 'string' && !canBeType(typeArr, 'string', 'number', 'integer')) || - (Array.isArray(value) && !canBeType(typeArr, 'array')) - ) { - if (typeof type !== 'undefined') { - return nls.localize('invalidTypeError', "Setting has an invalid type, expected {0}. Fix in JSON.", JSON.stringify(type)); - } + const typeArr = Array.isArray(type) ? type : [type]; + if (!typeArr.some(_type => valueValidatesAsType(value, _type))) { + return nls.localize('invalidTypeError', "Setting has an invalid type, expected {0}. Fix in JSON.", JSON.stringify(type)); } return; } +function valueValidatesAsType(value: any, type: string): boolean { + const valueType = typeof value; + if (type === 'boolean') { + return valueType === 'boolean'; + } else if (type === 'object') { + return value && !Array.isArray(value) && valueType === 'object'; + } else if (type === 'null') { + return value === null; + } else if (type === 'array') { + return Array.isArray(value); + } else if (type === 'string') { + return valueType === 'string'; + } else if (type === 'number' || type === 'integer') { + return valueType === 'number'; + } + + return true; +} + function getStringValidators(prop: IConfigurationPropertySchema) { let patternRegex: RegExp | undefined; if (typeof prop.pattern === 'string') { diff --git a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts similarity index 86% rename from src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts rename to src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts index a9ff80cc2b9..9fcbd932494 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts @@ -5,10 +5,10 @@ import * as assert from 'assert'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation'; +import { createValidator, getInvalidTypeError } from 'vs/workbench/services/preferences/common/preferencesValidation'; -suite('Preferences Model test', () => { +suite('Preferences Validation', () => { class Tester { private validator: (value: any) => string | null; @@ -334,4 +334,43 @@ suite('Preferences Model test', () => { arr.rejects(['a', 'a']).withMessage(`Array has duplicate items`); }); + + test('getInvalidTypeError', () => { + function testInvalidTypeError(value: any, type: string | string[], shouldValidate: boolean) { + const message = `value: ${value}, type: ${JSON.stringify(type)}, expected: ${shouldValidate ? 'valid' : 'invalid'}`; + if (shouldValidate) { + assert.ok(!getInvalidTypeError(value, type), message); + } else { + assert.ok(getInvalidTypeError(value, type), message); + } + } + + testInvalidTypeError(1, 'number', true); + testInvalidTypeError(1.5, 'number', true); + testInvalidTypeError([1], 'number', false); + testInvalidTypeError('1', 'number', false); + testInvalidTypeError({ a: 1 }, 'number', false); + testInvalidTypeError(null, 'number', false); + + testInvalidTypeError('a', 'string', true); + testInvalidTypeError('1', 'string', true); + testInvalidTypeError([], 'string', false); + testInvalidTypeError({}, 'string', false); + + testInvalidTypeError([1], 'array', true); + testInvalidTypeError([], 'array', true); + testInvalidTypeError([{}, [[]]], 'array', true); + testInvalidTypeError({ a: ['a'] }, 'array', false); + testInvalidTypeError('hello', 'array', false); + + testInvalidTypeError(true, 'boolean', true); + testInvalidTypeError('hello', 'boolean', false); + testInvalidTypeError(null, 'boolean', false); + testInvalidTypeError([true], 'boolean', false); + + testInvalidTypeError(null, 'null', true); + testInvalidTypeError(false, 'null', false); + testInvalidTypeError([null], 'null', false); + testInvalidTypeError('null', 'null', false); + }); }); From 5e5fdffcc7f508409c106adbd8fa33df32b0d3c0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Mar 2020 17:25:58 -0800 Subject: [PATCH 065/148] Add tooltip to settings editor tabs --- .../configuration/common/configuration.ts | 2 ++ .../preferences/browser/preferencesWidgets.ts | 18 +++++++++++------- .../browser/configurationService.ts | 19 ++++++++++++++++++- .../common/configurationEditingService.ts | 8 ++++---- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 16bf8c384f9..a53fb3acb14 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -123,6 +123,8 @@ export interface IConfigurationService { workspaceFolder: string[]; memory?: string[]; }; + + getConfigurationFileResource(target: ConfigurationTarget, resource: URI | null | undefined): URI | null; } export interface IConfigurationModel { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 45ab2bfe9a7..c7f48620f65 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -20,7 +20,7 @@ import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorE import { Position } from 'vs/editor/common/core/position'; import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -310,7 +310,8 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { constructor( action: IAction, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IContextMenuService private readonly contextMenuService: IContextMenuService + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(null, action); const workspace = this.contextService.getWorkspace(); @@ -407,7 +408,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { const workspace = this.contextService.getWorkspace(); if (this._folder) { this.labelElement.textContent = this._folder.name; - this.anchorElement.title = this._folder.name; + this.anchorElement.title = this.configurationService.getConfigurationFileResource(ConfigurationTarget.WORKSPACE_FOLDER, this._folder.uri)?.fsPath || ''; const detailsText = this.labelWithCount(this._action.label, total); this.detailsElement.textContent = detailsText; DOM.toggleClass(this.dropDownElement, 'hide', workspace.folders.length === 1 || !this._action.checked); @@ -418,6 +419,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { this.anchorElement.title = this._action.label; DOM.removeClass(this.dropDownElement, 'hide'); } + DOM.toggleClass(this.anchorElement, 'checked', this._action.checked); DOM.toggleClass(this.container, 'disabled', !this._action.enabled); } @@ -487,7 +489,8 @@ export class SettingsTargetsWidget extends Widget { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @ILabelService private readonly labelService: ILabelService + @ILabelService private readonly labelService: ILabelService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); this.options = options || {}; @@ -506,17 +509,16 @@ export class SettingsTargetsWidget extends Widget { })); this.userLocalSettings = new Action('userSettings', localize('userSettings', "User"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); - this.userLocalSettings.tooltip = this.userLocalSettings.label; + this.userLocalSettings.tooltip = this.configurationService.getConfigurationFileResource(ConfigurationTarget.USER_LOCAL, undefined)?.fsPath || ''; const remoteAuthority = this.environmentService.configuration.remoteAuthority; const hostLabel = remoteAuthority && this.labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority); const remoteSettingsLabel = localize('userSettingsRemote', "Remote") + (hostLabel ? ` [${hostLabel}]` : ''); this.userRemoteSettings = new Action('userSettingsRemote', remoteSettingsLabel, '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE)); - this.userRemoteSettings.tooltip = this.userRemoteSettings.label; + this.userRemoteSettings.tooltip = this.configurationService.getConfigurationFileResource(ConfigurationTarget.USER_REMOTE, undefined)?.fsPath || ''; this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE)); - this.workspaceSettings.tooltip = this.workspaceSettings.label; const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder"), '.settings-tab', false, (folder: IWorkspaceFolder | null) => this.updateTarget(folder ? folder.uri : ConfigurationTarget.USER_LOCAL)); @@ -591,6 +593,8 @@ export class SettingsTargetsWidget extends Widget { this.userRemoteSettings.enabled = !!(this.options.enableRemoteSettings && this.environmentService.configuration.remoteAuthority); this.workspaceSettings.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; this.folderSettings.getAction().enabled = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.contextService.getWorkspace().folders.length > 0; + + this.workspaceSettings.tooltip = this.configurationService.getConfigurationFileResource(ConfigurationTarget.WORKSPACE, undefined)?.fsPath || ''; } } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 4d2315b2e2d..71cb31b7de9 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -14,7 +14,7 @@ import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, FOLDER_SETTINGS_PATH } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; @@ -310,6 +310,23 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } } + public getConfigurationFileResource(target: ConfigurationTarget, resource: URI | null | undefined): URI | null { + let editableTarget: EditableConfigurationTarget; + if (target === ConfigurationTarget.USER || target === ConfigurationTarget.USER_LOCAL) { + editableTarget = EditableConfigurationTarget.USER_LOCAL; + } else if (target === ConfigurationTarget.USER_REMOTE) { + editableTarget = EditableConfigurationTarget.USER_REMOTE; + } else if (target === ConfigurationTarget.WORKSPACE) { + editableTarget = EditableConfigurationTarget.WORKSPACE; + } else if (target === ConfigurationTarget.WORKSPACE_FOLDER) { + editableTarget = EditableConfigurationTarget.WORKSPACE_FOLDER; + } else { + return null; + } + + return this.configurationEditingService?.getConfigurationFileResource(editableTarget, FOLDER_SETTINGS_PATH, resource); + } + private createWorkspace(arg: IWorkspaceInitializationPayload): Promise { if (isWorkspaceIdentifier(arg)) { return this.createMultiFolderWorkspace(arg); diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 573b4f0f546..e908891aa17 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -543,7 +543,7 @@ export class ConfigurationEditingService { const standaloneConfigurationMap = target === EditableConfigurationTarget.USER_LOCAL ? USER_STANDALONE_CONFIGURATIONS : WORKSPACE_STANDALONE_CONFIGURATIONS; const standaloneConfigurationKeys = Object.keys(standaloneConfigurationMap); for (const key of standaloneConfigurationKeys) { - const resource = this.getConfigurationFileResource(target, config, standaloneConfigurationMap[key], overrides.resource); + const resource = this.getConfigurationFileResource(target, standaloneConfigurationMap[key], overrides.resource); // Check for prefix if (config.key === key) { @@ -563,10 +563,10 @@ export class ConfigurationEditingService { let key = config.key; let jsonPath = overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier), key] : [key]; if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { - return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, config, '', null)), target }; + return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, '', null)), target }; } - const resource = this.getConfigurationFileResource(target, config, FOLDER_SETTINGS_PATH, overrides.resource); + const resource = this.getConfigurationFileResource(target, FOLDER_SETTINGS_PATH, overrides.resource); if (this.isWorkspaceConfigurationResource(resource)) { jsonPath = ['settings', ...jsonPath]; } @@ -578,7 +578,7 @@ export class ConfigurationEditingService { return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath); } - private getConfigurationFileResource(target: EditableConfigurationTarget, config: IConfigurationValue, relativePath: string, resource: URI | null | undefined): URI | null { + public getConfigurationFileResource(target: EditableConfigurationTarget, relativePath: string, resource: URI | null | undefined): URI | null { if (target === EditableConfigurationTarget.USER_LOCAL) { if (relativePath) { return resources.joinPath(resources.dirname(this.environmentService.settingsResource), relativePath); From e09c8ef0741debabb52fccc094ee726414acf085 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 19:01:10 -0500 Subject: [PATCH 066/148] Fix #90981 --- .../configuration/common/configuration.ts | 2 -- .../preferences/browser/preferencesWidgets.ts | 26 +++++++++++-------- .../browser/configurationService.ts | 19 +------------- .../common/configurationEditingService.ts | 2 +- .../preferences/browser/preferencesService.ts | 2 +- .../preferences/common/preferences.ts | 1 + 6 files changed, 19 insertions(+), 33 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index a53fb3acb14..16bf8c384f9 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -123,8 +123,6 @@ export interface IConfigurationService { workspaceFolder: string[]; memory?: string[]; }; - - getConfigurationFileResource(target: ConfigurationTarget, resource: URI | null | undefined): URI | null; } export interface IConfigurationModel { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index c7f48620f65..3e9e575fde1 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -20,7 +20,7 @@ import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorE import { Position } from 'vs/editor/common/core/position'; import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -32,7 +32,7 @@ import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticip import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { ISettingsGroup, IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { isEqual } from 'vs/base/common/resources'; import { registerIcon, Codicon } from 'vs/base/common/codicons'; @@ -311,7 +311,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { action: IAction, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super(null, action); const workspace = this.contextService.getWorkspace(); @@ -401,14 +401,14 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { } } - private update(): void { + private async update(): Promise { let total = 0; this._folderSettingCounts.forEach(n => total += n); const workspace = this.contextService.getWorkspace(); if (this._folder) { this.labelElement.textContent = this._folder.name; - this.anchorElement.title = this.configurationService.getConfigurationFileResource(ConfigurationTarget.WORKSPACE_FOLDER, this._folder.uri)?.fsPath || ''; + this.anchorElement.title = (await this.preferencesService.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, this._folder.uri))?.fsPath || ''; const detailsText = this.labelWithCount(this._action.label, total); this.detailsElement.textContent = detailsText; DOM.toggleClass(this.dropDownElement, 'hide', workspace.folders.length === 1 || !this._action.checked); @@ -490,7 +490,7 @@ export class SettingsTargetsWidget extends Widget { @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @ILabelService private readonly labelService: ILabelService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IPreferencesService private readonly preferencesSerivce: IPreferencesService, ) { super(); this.options = options || {}; @@ -509,14 +509,19 @@ export class SettingsTargetsWidget extends Widget { })); this.userLocalSettings = new Action('userSettings', localize('userSettings', "User"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); - this.userLocalSettings.tooltip = this.configurationService.getConfigurationFileResource(ConfigurationTarget.USER_LOCAL, undefined)?.fsPath || ''; + this.preferencesSerivce.getEditableSettingsURI(ConfigurationTarget.USER_LOCAL).then(uri => { + // Don't wait to create UI on resolving remote + this.userLocalSettings.tooltip = uri?.fsPath || ''; + }); const remoteAuthority = this.environmentService.configuration.remoteAuthority; const hostLabel = remoteAuthority && this.labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority); const remoteSettingsLabel = localize('userSettingsRemote', "Remote") + (hostLabel ? ` [${hostLabel}]` : ''); this.userRemoteSettings = new Action('userSettingsRemote', remoteSettingsLabel, '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE)); - this.userRemoteSettings.tooltip = this.configurationService.getConfigurationFileResource(ConfigurationTarget.USER_REMOTE, undefined)?.fsPath || ''; + this.preferencesSerivce.getEditableSettingsURI(ConfigurationTarget.USER_REMOTE).then(uri => { + this.userRemoteSettings.tooltip = uri?.fsPath || ''; + }); this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE)); @@ -588,15 +593,14 @@ export class SettingsTargetsWidget extends Widget { return Promise.resolve(undefined); } - private update(): void { + private async update(): Promise { DOM.toggleClass(this.settingsSwitcherBar.domNode, 'empty-workbench', this.contextService.getWorkbenchState() === WorkbenchState.EMPTY); this.userRemoteSettings.enabled = !!(this.options.enableRemoteSettings && this.environmentService.configuration.remoteAuthority); this.workspaceSettings.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; this.folderSettings.getAction().enabled = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.contextService.getWorkspace().folders.length > 0; - this.workspaceSettings.tooltip = this.configurationService.getConfigurationFileResource(ConfigurationTarget.WORKSPACE, undefined)?.fsPath || ''; + this.workspaceSettings.tooltip = (await this.preferencesSerivce.getEditableSettingsURI(ConfigurationTarget.WORKSPACE))?.fsPath || ''; } - } export interface SearchOptions extends IInputOptions { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 71cb31b7de9..4d2315b2e2d 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -14,7 +14,7 @@ import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, FOLDER_SETTINGS_PATH } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; @@ -310,23 +310,6 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } } - public getConfigurationFileResource(target: ConfigurationTarget, resource: URI | null | undefined): URI | null { - let editableTarget: EditableConfigurationTarget; - if (target === ConfigurationTarget.USER || target === ConfigurationTarget.USER_LOCAL) { - editableTarget = EditableConfigurationTarget.USER_LOCAL; - } else if (target === ConfigurationTarget.USER_REMOTE) { - editableTarget = EditableConfigurationTarget.USER_REMOTE; - } else if (target === ConfigurationTarget.WORKSPACE) { - editableTarget = EditableConfigurationTarget.WORKSPACE; - } else if (target === ConfigurationTarget.WORKSPACE_FOLDER) { - editableTarget = EditableConfigurationTarget.WORKSPACE_FOLDER; - } else { - return null; - } - - return this.configurationEditingService?.getConfigurationFileResource(editableTarget, FOLDER_SETTINGS_PATH, resource); - } - private createWorkspace(arg: IWorkspaceInitializationPayload): Promise { if (isWorkspaceIdentifier(arg)) { return this.createMultiFolderWorkspace(arg); diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index e908891aa17..6a76e88e019 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -578,7 +578,7 @@ export class ConfigurationEditingService { return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath); } - public getConfigurationFileResource(target: EditableConfigurationTarget, relativePath: string, resource: URI | null | undefined): URI | null { + private getConfigurationFileResource(target: EditableConfigurationTarget, relativePath: string, resource: URI | null | undefined): URI | null { if (target === EditableConfigurationTarget.USER_LOCAL) { if (relativePath) { return resources.joinPath(resources.dirname(this.environmentService.settingsResource), relativePath); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index bc872a3633e..6334bcafbf6 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -518,7 +518,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this._defaultUserSettingsContentModel; } - private async getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise { + public async getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise { switch (configurationTarget) { case ConfigurationTarget.USER: case ConfigurationTarget.USER_LOCAL: diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index e39388c5e71..f7795c59a86 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -207,6 +207,7 @@ export interface IPreferencesService { switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise; openGlobalKeybindingSettings(textual: boolean): Promise; openDefaultKeybindingsFile(): Promise; + getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise; } export function getSettingsTargetName(target: ConfigurationTarget, resource: URI, workspaceContextService: IWorkspaceContextService): string { From 1f2874c7b85b097d93409d36f100ed91f6547485 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 8 May 2020 19:02:03 -0500 Subject: [PATCH 067/148] typo --- .../contrib/preferences/browser/preferencesWidgets.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 3e9e575fde1..d0f5ac5841e 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -490,7 +490,7 @@ export class SettingsTargetsWidget extends Widget { @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @ILabelService private readonly labelService: ILabelService, - @IPreferencesService private readonly preferencesSerivce: IPreferencesService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super(); this.options = options || {}; @@ -509,7 +509,7 @@ export class SettingsTargetsWidget extends Widget { })); this.userLocalSettings = new Action('userSettings', localize('userSettings', "User"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); - this.preferencesSerivce.getEditableSettingsURI(ConfigurationTarget.USER_LOCAL).then(uri => { + this.preferencesService.getEditableSettingsURI(ConfigurationTarget.USER_LOCAL).then(uri => { // Don't wait to create UI on resolving remote this.userLocalSettings.tooltip = uri?.fsPath || ''; }); @@ -519,7 +519,7 @@ export class SettingsTargetsWidget extends Widget { const remoteSettingsLabel = localize('userSettingsRemote', "Remote") + (hostLabel ? ` [${hostLabel}]` : ''); this.userRemoteSettings = new Action('userSettingsRemote', remoteSettingsLabel, '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE)); - this.preferencesSerivce.getEditableSettingsURI(ConfigurationTarget.USER_REMOTE).then(uri => { + this.preferencesService.getEditableSettingsURI(ConfigurationTarget.USER_REMOTE).then(uri => { this.userRemoteSettings.tooltip = uri?.fsPath || ''; }); @@ -599,7 +599,7 @@ export class SettingsTargetsWidget extends Widget { this.workspaceSettings.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; this.folderSettings.getAction().enabled = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.contextService.getWorkspace().folders.length > 0; - this.workspaceSettings.tooltip = (await this.preferencesSerivce.getEditableSettingsURI(ConfigurationTarget.WORKSPACE))?.fsPath || ''; + this.workspaceSettings.tooltip = (await this.preferencesService.getEditableSettingsURI(ConfigurationTarget.WORKSPACE))?.fsPath || ''; } } From 56c6946bf285da6be5fdb92ce6f3f5dafaa88772 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 9 May 2020 20:46:12 -0500 Subject: [PATCH 068/148] Fix issue with searching using workspace folder name. Add file search integration tests Fix #97352 --- .../services/search/common/search.ts | 8 +- .../services/search/node/fileSearch.ts | 22 ++-- .../test/node/fileSearch.integrationTest.ts | 111 ++++++++++++++++++ .../search/test/node/rawSearchService.test.ts | 8 +- .../test/node/textSearch.integrationTest.ts | 2 +- 5 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 68ebc39b1d2..806f1706358 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -183,7 +183,7 @@ export function resultIsMatch(result: ITextSearchResult): result is ITextSearchM } export interface IProgressMessage { - message?: string; + message: string; } export type ISearchProgressItem = IFileMatch | IProgressMessage; @@ -192,8 +192,8 @@ export function isFileMatch(p: ISearchProgressItem): p is IFileMatch { return !!(p).resource; } -export function isProgressMessage(p: ISearchProgressItem): p is IProgressMessage { - return !isFileMatch(p); +export function isProgressMessage(p: ISearchProgressItem | ISerializedSearchProgressItem): p is IProgressMessage { + return !!(p as IProgressMessage).message; } export interface ISearchCompleteStats { @@ -468,7 +468,7 @@ export interface IRawFileMatch { * * If not given, the search algorithm should use `relativePath`. */ - searchPath?: string; + searchPath: string | undefined; } export interface ISearchEngine { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 97fe8231b86..719ad873b50 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -24,9 +24,8 @@ import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFil import { spawnRipgrepCmd } from './ripgrepFileSearch'; import { prepareQuery } from 'vs/base/common/fuzzyScorer'; -interface IDirectoryEntry { +interface IDirectoryEntry extends IRawFileMatch { base: string; - relativePath: string; basename: string; } @@ -122,7 +121,7 @@ export class FileWalker { } // File: Check for match on file pattern and include pattern - this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */ }); + this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */, searchPath: undefined }); }); this.cmdSW = StopWatch.create(false); @@ -260,7 +259,7 @@ export class FileWalker { } // TODO: Optimize siblings clauses with ripgrep here. - this.addDirectoryEntries(tree, rootFolder, relativeFiles, onResult); + this.addDirectoryEntries(folderQuery, tree, rootFolder, relativeFiles, onResult); if (last) { this.matchDirectoryTree(tree, rootFolder, onResult); @@ -389,13 +388,17 @@ export class FileWalker { return tree; } - private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: string, relativeFiles: string[], onResult: (result: IRawFileMatch) => void) { + private addDirectoryEntries(folderQuery: IFolderQuery, { pathToEntries }: IDirectoryTree, base: string, relativeFiles: string[], onResult: (result: IRawFileMatch) => void) { // Support relative paths to files from a root resource (ignores excludes) if (relativeFiles.indexOf(this.filePattern) !== -1) { - this.matchFile(onResult, { base: base, relativePath: this.filePattern }); + this.matchFile(onResult, { + base, + relativePath: this.filePattern, + searchPath: this.getSearchPath(folderQuery, this.filePattern) + }); } - function add(relativePath: string) { + const add = (relativePath: string) => { const basename = path.basename(relativePath); const dirname = path.dirname(relativePath); let entries = pathToEntries[dirname]; @@ -406,9 +409,10 @@ export class FileWalker { entries.push({ base, relativePath, - basename + basename, + searchPath: this.getSearchPath(folderQuery, relativePath), }); - } + }; relativeFiles.forEach(add); } diff --git a/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts new file mode 100644 index 00000000000..427de8a4fd9 --- /dev/null +++ b/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { getPathFromAmdModule } from 'vs/base/common/amd'; +import * as path from 'vs/base/common/path'; +import { URI } from 'vs/base/common/uri'; +import { IFileQuery, IFolderQuery, ISerializedSearchProgressItem, isProgressMessage, QueryType } from 'vs/workbench/services/search/common/search'; +import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; + +const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); +const TEST_FIXTURES2 = path.normalize(getPathFromAmdModule(require, './fixtures2')); +const EXAMPLES_FIXTURES = path.join(TEST_FIXTURES, 'examples'); +const MORE_FIXTURES = path.join(TEST_FIXTURES, 'more'); +const TEST_ROOT_FOLDER: IFolderQuery = { folder: URI.file(TEST_FIXTURES) }; +const ROOT_FOLDER_QUERY: IFolderQuery[] = [ + TEST_ROOT_FOLDER +]; + +const MULTIROOT_QUERIES: IFolderQuery[] = [ + { folder: URI.file(EXAMPLES_FIXTURES), folderName: 'examples_folder' }, + { folder: URI.file(MORE_FIXTURES) } +]; + +async function doSearchTest(query: IFileQuery, expectedResultCount: number | Function): Promise { + const svc = new SearchService(); + + const results: ISerializedSearchProgressItem[] = []; + await svc.doFileSearch(query, e => { + if (!isProgressMessage(e)) { + if (Array.isArray(e)) { + results.push(...e); + } else { + results.push(e); + } + } + }); + + assert.equal(results.length, expectedResultCount, `rg ${results.length} !== ${expectedResultCount}`); +} + +suite('FileSearch-integration', function () { + this.timeout(1000 * 60); // increase timeout for this suite + + test('File - simple', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: ROOT_FOLDER_QUERY + }; + + return doSearchTest(config, 14); + }); + + test('File - filepattern', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: ROOT_FOLDER_QUERY, + filePattern: 'anotherfile' + }; + + return doSearchTest(config, 1); + }); + + test('File - exclude', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: ROOT_FOLDER_QUERY, + filePattern: 'file', + excludePattern: { '**/anotherfolder/**': true } + }; + + return doSearchTest(config, 2); + }); + + test('File - multiroot', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: MULTIROOT_QUERIES, + filePattern: 'file', + excludePattern: { '**/anotherfolder/**': true } + }; + + return doSearchTest(config, 2); + }); + + test('File - multiroot with folder name', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: MULTIROOT_QUERIES, + filePattern: 'examples_folder anotherfile' + }; + + return doSearchTest(config, 1); + }); + + test('File - multiroot with folder name and sibling exclude', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: [ + { folder: URI.file(TEST_FIXTURES), folderName: 'folder1' }, + { folder: URI.file(TEST_FIXTURES2) } + ], + filePattern: 'folder1 site', + excludePattern: { '*.css': { when: '$(basename).less' } } + }; + + return doSearchTest(config, 1); + }); +}); diff --git a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts index b61f702fc56..1626179ccab 100644 --- a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts @@ -83,6 +83,7 @@ suite('RawSearchService', () => { const rawMatch: IRawFileMatch = { base: path.normalize('/some'), relativePath: 'where', + searchPath: undefined }; const match: ISerializedFileMatch = { @@ -232,7 +233,8 @@ suite('RawSearchService', () => { base: path.normalize('/some/where'), relativePath, basename: relativePath, - size: 3 + size: 3, + searchPath: undefined })); const Engine = TestSearchEngine.bind(null, () => matches.shift()!); const service = new RawSearchService(); @@ -291,7 +293,8 @@ suite('RawSearchService', () => { base: path.normalize('/some/where'), relativePath, basename: relativePath, - size: 3 + size: 3, + searchPath: undefined })); const Engine = TestSearchEngine.bind(null, () => matches.shift()!); const service = new RawSearchService(); @@ -340,6 +343,7 @@ suite('RawSearchService', () => { matches.push({ base: path.normalize('/some/where'), relativePath: 'bc', + searchPath: undefined }); const results: any[] = []; const cb: IProgressCallback = value => { diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index 6e8d77a82a5..1d2ad24fb75 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -46,7 +46,7 @@ function doSearchTest(query: ITextQuery, expectedResultCount: number | Function) }); } -suite('Search-integration', function () { +suite('TextSearch-integration', function () { this.timeout(1000 * 60); // increase timeout for this suite test('Text: GameOfLife', () => { From ee8c99a27f163231c1a871103038caf76f697090 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 10 May 2020 08:54:33 +0200 Subject: [PATCH 069/148] :up: playwright@1.0.1 --- build/.cachesalt | 2 +- package.json | 2 +- yarn.lock | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build/.cachesalt b/build/.cachesalt index 339d2d379fa..3f2ee542ad5 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2019-08-30T20:24:23.714Z +2020-10-05T20:24:23.714Z diff --git a/package.json b/package.json index d2d1082d47b..61e48f8384f 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", - "playwright": "0.15.0", + "playwright": "1.0.1", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", diff --git a/yarn.lock b/yarn.lock index f9925bf225d..9aac06278c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7012,10 +7012,10 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -playwright-core@=0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.15.0.tgz#c605c98a13c81d5a2e2691f15d04758cf302c20a" - integrity sha512-uTm4PoF2U3iXkLMMG9vlTxlGfO8atQGAHDxqi8xV7hEjNSYeLTU7c6HN5zwadeHRVuBbNsZ4yqu9u4hoqC7uxQ== +playwright-core@=1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.0.1.tgz#823b6f1afa16917ffd418f3cec0c14688a985738" + integrity sha512-a71FjUDRFqWLG3VBAojVen2TaZiXkuog+ZmI0Nh0+/QndFUbbW3kameOfUTMXFvLUGWx2ipERZx6EQTJMEQDMA== dependencies: debug "^4.1.1" extract-zip "^2.0.0" @@ -7028,12 +7028,12 @@ playwright-core@=0.15.0: rimraf "^3.0.2" ws "^6.1.0" -playwright@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.15.0.tgz#bf5c3bb8404975aba78459310742388c08438431" - integrity sha512-UGHkQz8DT43uJ0KgMh2rmj8BI4FE5ReQJ9nm5mG68tt1Cj2sXPdM2b05qptfYYBPtQRetQqtJTauZ6rlCDemaQ== +playwright@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.0.1.tgz#326d479829a3505799ddc9988cc8decf5a7f8376" + integrity sha512-kVTE7uvZ7OcDVOBx7MVArUm2nbzzzpauKV9tuVIAH6vWGsOWbGGALUoTWMzNDzsPPTBJXXmxzC4KgI2zN+kVhw== dependencies: - playwright-core "=0.15.0" + playwright-core "=1.0.1" plist@^3.0.1: version "3.0.1" From 5356ceb23cca764874d675fc01f3c09839cb26f0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 10 May 2020 09:50:13 +0200 Subject: [PATCH 070/148] windows - ensure new-window request has right window context (fix #97172) --- src/vs/code/electron-main/app.ts | 4 +- .../electron-main/electronMainService.ts | 5 ++- .../launch/electron-main/launchMainService.ts | 2 +- .../platform/menubar/electron-main/menubar.ts | 8 ++-- .../platform/windows/electron-main/windows.ts | 9 ++++- .../electron-main/windowsMainService.ts | 38 +++++++++++-------- 6 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 3a20695a397..fd287c25045 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -125,7 +125,7 @@ export class CodeApplication extends Disposable { // Mac only event: open new window when we get activated if (!hasVisibleWindows && this.windowsMainService) { - this.windowsMainService.openEmptyWindow(OpenContext.DOCK); + this.windowsMainService.openEmptyWindow({ context: OpenContext.DOCK }); } }); @@ -258,7 +258,7 @@ export class CodeApplication extends Disposable { app.on('new-window-for-tab', () => { if (this.windowsMainService) { - this.windowsMainService.openEmptyWindow(OpenContext.DESKTOP); //macOS native tab "+" button + this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button } }); diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index a8cc247f89d..c21fb966278 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -114,7 +114,10 @@ export class ElectronMainService implements IElectronMainService { } private async doOpenEmptyWindow(windowId: number | undefined, options?: IOpenEmptyWindowOptions): Promise { - this.windowsMainService.openEmptyWindow(OpenContext.API, options); + this.windowsMainService.openEmptyWindow({ + context: OpenContext.API, + contextWindowId: windowId + }, options); } async toggleFullScreen(windowId: number | undefined): Promise { diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 17698833e34..c2ede9faa64 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -84,7 +84,7 @@ export class LaunchMainService implements ILaunchMainService { // Create a window if there is none if (this.windowsMainService.getWindowCount() === 0) { - const window = this.windowsMainService.openEmptyWindow(OpenContext.DESKTOP)[0]; + const window = this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP })[0]; whenWindowReady = window.ready(); } diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index dd0251290f2..ac66913edbf 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -61,7 +61,7 @@ export class Menubar { private keybindings: { [commandId: string]: IMenubarKeybinding }; - private fallbackMenuHandlers: { [id: string]: (menuItem: MenuItem, browserWindow: BrowserWindow, event: Event) => void } = {}; + private readonly fallbackMenuHandlers: { [id: string]: (menuItem: MenuItem, browserWindow: BrowserWindow, event: Event) => void } = Object.create(null); constructor( @IUpdateService private readonly updateService: IUpdateService, @@ -113,8 +113,8 @@ export class Menubar { private addFallbackHandlers(): void { // File Menu Items - this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = () => this.windowsMainService.openEmptyWindow(OpenContext.MENU); - this.fallbackMenuHandlers['workbench.action.newWindow'] = () => this.windowsMainService.openEmptyWindow(OpenContext.MENU); + this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = (menuItem, win, event) => this.windowsMainService.openEmptyWindow({ context: OpenContext.MENU, contextWindowId: win.id }); + this.fallbackMenuHandlers['workbench.action.newWindow'] = (menuItem, win, event) => this.windowsMainService.openEmptyWindow({ context: OpenContext.MENU, contextWindowId: win.id }); this.fallbackMenuHandlers['workbench.action.files.openFileFolder'] = (menuItem, win, event) => this.electronMainService.pickFileFolderAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.electronMainService.pickWorkspaceAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); @@ -266,7 +266,7 @@ export class Menubar { this.appMenuInstalled = true; const dockMenu = new Menu(); - dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openEmptyWindow(OpenContext.DOCK) })); + dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openEmptyWindow({ context: OpenContext.DOCK }) })); app.dock.setMenu(dockMenu); } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 0db6846abd9..5c6facab0a9 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -105,7 +105,7 @@ export interface IWindowsMainService { readonly onWindowsCountChanged: Event; open(openConfig: IOpenConfiguration): ICodeWindow[]; - openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[]; + openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[]; openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[]; sendToFocused(channel: string, ...args: any[]): void; @@ -118,9 +118,12 @@ export interface IWindowsMainService { getWindowCount(): number; } -export interface IOpenConfiguration { +export interface IBaseOpenConfiguration { readonly context: OpenContext; readonly contextWindowId?: number; +} + +export interface IOpenConfiguration extends IBaseOpenConfiguration { readonly cli: ParsedArgs; readonly userEnv?: IProcessEnvironment; readonly urisToOpen?: IWindowOpenable[]; @@ -136,3 +139,5 @@ export interface IOpenConfiguration { readonly initialStartup?: boolean; readonly noRecentEntry?: boolean; } + +export interface IOpenEmptyConfiguration extends IBaseOpenConfiguration { } diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 266c235a0fe..cdd6731748d 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -24,7 +24,7 @@ import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri, INativeWindowConfiguration, OpenContext, IAddFoldersRequest, IPathsToWaitFor } from 'vs/platform/windows/node/window'; import { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; -import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode, IOpenEmptyConfiguration } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; import { IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension, IRecent } from 'vs/platform/workspaces/common/workspaces'; @@ -393,7 +393,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic }; } - openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[] { + openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[] { let cli = this.environmentService.args; const remote = options?.remoteAuthority; if (cli && (cli.remote !== remote)) { @@ -403,7 +403,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const forceReuseWindow = options?.forceReuseWindow; const forceNewWindow = !forceReuseWindow; - return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow }); + return this.open({ ...openConfig, cli, forceEmpty: true, forceNewWindow, forceReuseWindow }); } open(openConfig: IOpenConfiguration): ICodeWindow[] { @@ -474,7 +474,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Make sure to pass focus to the most relevant of the windows if we open multiple if (usedWindows.length > 1) { - const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length); let focusLastOpened = true; let focusLastWindow = true; @@ -753,15 +752,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined); for (let i = 0; i < emptyToOpen; i++) { - usedWindows.push(this.openInBrowserWindow({ - userEnv: openConfig.userEnv, - cli: openConfig.cli, - initialStartup: openConfig.initialStartup, - remoteAuthority, - forceNewWindow: openFolderInNewWindow, - forceNewTabbedWindow: openConfig.forceNewTabbedWindow, - fileInputs - })); + usedWindows.push(this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, fileInputs)); // Reset these because we handled them fileInputs = undefined; @@ -801,12 +792,29 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return window; } + private doOpenEmpty(openConfig: IOpenConfiguration, forceNewWindow: boolean, remoteAuthority: string | undefined, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow { + if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { + windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/97172 + } + + return this.openInBrowserWindow({ + userEnv: openConfig.userEnv, + cli: openConfig.cli, + initialStartup: openConfig.initialStartup, + remoteAuthority, + forceNewWindow, + forceNewTabbedWindow: openConfig.forceNewTabbedWindow, + fileInputs, + windowToUse + }); + } + private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow { if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/Microsoft/vscode/issues/49587 } - const browserWindow = this.openInBrowserWindow({ + return this.openInBrowserWindow({ userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, @@ -818,8 +826,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic forceNewTabbedWindow: openConfig.forceNewTabbedWindow, windowToUse }); - - return browserWindow; } private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] { From 67cfd21b80752b134416ec517e0efd85bc8e86d2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 10 May 2020 10:46:09 +0200 Subject: [PATCH 071/148] Feedback on "Ok to install". Fixes #92139 --- .../services/extensions/electron-browser/extensionService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index fd7b5856896..88f766b777b 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -614,7 +614,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } else { // Install the Extension and reload the window to handle. - const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window.\nOK to install?", recommendation.friendlyName); + const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window.\nDo you want to install the extension?", recommendation.friendlyName); this._notificationService.prompt(Severity.Info, message, [{ label: nls.localize('install', 'Install and Reload'), From ba33738bb3db01e37e3addcdf776c5a68d64671c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 8 May 2020 22:27:52 -0700 Subject: [PATCH 072/148] Pick up TS 3.9.2 insiders --- extensions/package.json | 2 +- extensions/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index 343e3053756..8744f4dff90 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.9.1-rc" + "typescript": "^3.9.2-insiders.20200509" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 0a5236436a5..4e0ca82acd1 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.9.1-rc: - version "3.9.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.1-rc.tgz#81d5a5a0a597e224b6e2af8dffb46524b2eaf5f3" - integrity sha512-+cPv8L2Vd4KidCotqi2wjegBZ5n47CDRUu/QiLVu2YbeXAz78hIfcai9ziBiNI6JTGTVwUqXRug2UZxDcxhvFw== +typescript@^3.9.2-insiders.20200509: + version "3.9.2-insiders.20200509" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2-insiders.20200509.tgz#8c90ed86a91f9692f10f5ac9c1fd6cb241419e6c" + integrity sha512-AAbhs55BZMbyHGfJd0pNfO3+B6jjPpa38zgaIb9MRExkRGLkIUpbUetoh+HgmM5LAtg128sHGiwhLc49pOcgFw== From 6177de0c0f52b6977b5d2f2bdf9482620c4fd5c5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 11 May 2020 08:24:42 +0200 Subject: [PATCH 073/148] debt - remove unused method from launch service --- .../platform/launch/electron-main/launchMainService.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index c2ede9faa64..8775dd82ad2 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -6,7 +6,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURLService } from 'vs/platform/url/common/url'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ParsedArgs } from 'vs/platform/environment/node/argv'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowSettings } from 'vs/platform/windows/common/windows'; @@ -56,7 +55,6 @@ export interface ILaunchMainService { start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise; getMainProcessId(): Promise; getMainProcessInfo(): Promise; - getLogsPath(): Promise; getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]>; } @@ -69,7 +67,6 @@ export class LaunchMainService implements ILaunchMainService { @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IURLService private readonly urlService: IURLService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService ) { } @@ -226,12 +223,6 @@ export class LaunchMainService implements ILaunchMainService { }); } - getLogsPath(): Promise { - this.logService.trace('Received request for logs path from other instance.'); - - return Promise.resolve(this.environmentService.logsPath); - } - getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]> { const windows = this.windowsMainService.getWindows(); const promises: Promise[] = windows.map(window => { From 210263b177190c53e5416e8ece42dc78895c761e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 11 May 2020 09:38:00 +0200 Subject: [PATCH 074/148] menu - simpler lazy instantiation --- .../electron-main/menubarMainService.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/menubar/electron-main/menubarMainService.ts b/src/vs/platform/menubar/electron-main/menubarMainService.ts index 3cdb14892c0..7cadd57ecdb 100644 --- a/src/vs/platform/menubar/electron-main/menubarMainService.ts +++ b/src/vs/platform/menubar/electron-main/menubarMainService.ts @@ -13,28 +13,26 @@ export class MenubarMainService implements IMenubarService { _serviceBrand: undefined; - private _menubar: Menubar | undefined; + private menubar: Promise; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService ) { - // Install Menu - this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { - this._menubar = this.instantiationService.createInstance(Menubar); - }); + this.menubar = this.installMenuBarAfterWindowOpen(); } - updateMenubar(windowId: number, menus: IMenubarData): Promise { - return this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { - this.logService.trace('menubarService#updateMenubar', windowId); + private async installMenuBarAfterWindowOpen(): Promise { + await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen); - if (this._menubar) { - this._menubar.updateMenu(menus, windowId); - } + return this.instantiationService.createInstance(Menubar); + } - return undefined; - }); + async updateMenubar(windowId: number, menus: IMenubarData): Promise { + this.logService.trace('menubarService#updateMenubar', windowId); + + const menubar = await this.menubar; + menubar.updateMenu(menus, windowId); } } From 0e70726bf900d64b85773d3dc48b100ec66cce93 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 11 May 2020 09:38:17 +0200 Subject: [PATCH 075/148] actions - remove some unused event/context from run() --- src/vs/base/browser/ui/tree/treeDefaults.ts | 2 +- .../contrib/localizations/browser/localizationsActions.ts | 2 +- src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts | 2 +- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 2 +- .../contrib/terminal/electron-browser/terminalRemote.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/tree/treeDefaults.ts b/src/vs/base/browser/ui/tree/treeDefaults.ts index 944efecd207..834ab449fdd 100644 --- a/src/vs/base/browser/ui/tree/treeDefaults.ts +++ b/src/vs/base/browser/ui/tree/treeDefaults.ts @@ -13,7 +13,7 @@ export class CollapseAllAction extends Action { super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'collapse-all', enabled); } - async run(context?: any): Promise { + async run(): Promise { this.viewer.collapseAll(); this.viewer.setSelection([]); this.viewer.setFocus([]); diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index c4d4036dc85..ff17e5b4347 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -46,7 +46,7 @@ export class ConfigureLocaleAction extends Action { .concat({ label: localize('installAdditionalLanguages', "Install additional languages...") }); } - public async run(event?: any): Promise { + public async run(): Promise { const languageOptions = await this.getLanguageOptions(); const currentLanguageIndex = firstIndex(languageOptions, l => l.label === language); diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index cbe96131cb7..4866dd1e823 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -145,7 +145,7 @@ export class ManageAutomaticTaskRunning extends Action { super(id, label); } - public async run(event?: any): Promise { + public async run(): Promise { const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.allowAutomaticTasks', "Allow Automatic Tasks in Folder") }; const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.disallowAutomaticTasks', "Disallow Automatic Tasks in Folder") }; const value = await this.quickInputService.pick([allowItem, disallowItem], { canPickMany: false }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 594bf53132e..dfaef3ea7e4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -236,7 +236,7 @@ export class SplitTerminalAction extends Action { super(id, label, SplitTerminalAction.HORIZONTAL_CLASS); } - public async run(event?: any): Promise { + public async run(): Promise { await this._terminalService.doWithActiveInstance(async t => { const cwd = await getCwdForSplit(this._terminalService.configHelper, t, this._workspaceContextService.getWorkspace().folders, this._commandService); if (cwd === undefined) { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts index bf4e6c98fbf..1547e863513 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts @@ -29,7 +29,7 @@ export class CreateNewLocalTerminalAction extends Action { super(id, label); } - public run(event?: any): Promise { + public run(): Promise { const instance = this.terminalService.createTerminal({ cwd: URI.file(homedir()) }); if (!instance) { return Promise.resolve(undefined); From 44bcbe587d7f265ab0457c7931a7baab2419ad66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 11 May 2020 09:47:33 +0200 Subject: [PATCH 076/148] fix retriggering broken builds --- build/azure-pipelines/product-compile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 1f665c8b3de..c3db41e80d5 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -76,7 +76,6 @@ steps: set -e yarn generate-github-config displayName: Generate GitHub config - condition: succeeded() env: OSS_GITHUB_ID: "a5d3c261b032765a78de" OSS_GITHUB_SECRET: $(oss-github-client-secret) @@ -94,6 +93,7 @@ steps: VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret) GITHUB_APP_ID: "Iv1.ae51e546bef24ff1" GITHUB_APP_SECRET: $(github-app-client-secret) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) - script: | set -e From 13df275761d906f37a53ceef7eb274630ffc418e Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 11 May 2020 11:12:05 +0200 Subject: [PATCH 077/148] debug: remove support to open config file to the side fixes #97470 --- .../workbench/contrib/debug/browser/debugActions.ts | 5 ++--- .../workbench/contrib/debug/browser/debugCommands.ts | 2 +- .../debug/browser/debugConfigurationManager.ts | 12 ++++++------ .../contrib/debug/browser/debugQuickAccess.ts | 2 +- .../workbench/contrib/debug/browser/debugService.ts | 6 +++--- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 9c62710d39e..a0ad5e6d2b8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -81,7 +81,7 @@ export class ConfigureAction extends AbstractDebugAction { this.class = configurationManager.selectedConfiguration.name ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification'; } - async run(event?: any): Promise { + async run(): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.notificationService.info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration.")); return; @@ -105,8 +105,7 @@ export class ConfigureAction extends AbstractDebugAction { } if (launch) { - const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); - return launch.openConfigFile(sideBySide, false); + return launch.openConfigFile(false); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 944836a1b5e..cc3f614f34c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -504,7 +504,7 @@ export function registerCommands(): void { const launch = manager.getLaunches().find(l => l.uri.toString() === launchUri) || manager.selectedConfiguration.launch; if (launch) { - const { editor, created } = await launch.openConfigFile(false, false); + const { editor, created } = await launch.openConfigFile(false); if (editor && !created) { const codeEditor = editor.getControl(); if (codeEditor) { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 64e14cfea9f..14385280e0b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -22,7 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ICommandService } from 'vs/platform/commands/common/commands'; import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IConfigPresentation } from 'vs/workbench/contrib/debug/common/debug'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; -import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -635,7 +635,7 @@ class Launch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolderValue; } - async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> { + async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> { const resource = this.uri; let created = false; let content = ''; @@ -681,7 +681,7 @@ class Launch extends AbstractLaunch implements ILaunch { pinned: created, revealIfVisible: true }, - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + }, ACTIVE_GROUP); return ({ editor: withUndefinedAsNull(editor), @@ -715,12 +715,12 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').workspaceValue; } - async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { + async openConfigFile(preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { const editor = await this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration!, options: { preserveFocus } - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + }, ACTIVE_GROUP); return ({ editor: withUndefinedAsNull(editor), @@ -758,7 +758,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').userValue; } - async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { + async openConfigFile(preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus }); return ({ editor: withUndefinedAsNull(editor), diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index cbf747da6a7..e4c1c9427a2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -60,7 +60,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { - config.launch.openConfigFile(false, false); + config.launch.openConfigFile(false); return TriggerAction.CLOSE_PICKER; }, diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 401080dc290..203c7f85a30 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -405,7 +405,7 @@ export class DebugService implements IDebugService { const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, initCancellationToken.token); if (!cfg) { if (launch && type && cfg === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". - await launch.openConfigFile(false, true, type, initCancellationToken.token); + await launch.openConfigFile(true, type, initCancellationToken.token); } return false; } @@ -439,7 +439,7 @@ export class DebugService implements IDebugService { await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type.")); } if (launch && !initCancellationToken.token.isCancellationRequested) { - await launch.openConfigFile(false, true, undefined, initCancellationToken.token); + await launch.openConfigFile(true, undefined, initCancellationToken.token); } return false; @@ -447,7 +447,7 @@ export class DebugService implements IDebugService { } if (launch && type && configByProviders === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". - await launch.openConfigFile(false, true, type, initCancellationToken.token); + await launch.openConfigFile(true, type, initCancellationToken.token); } return false; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 601a098796e..51113e0b135 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -717,7 +717,7 @@ export interface ILaunch { /** * Opens the launch.json file. Creates if it does not exist. */ - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }>; + openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }>; } // Debug service interfaces From c650757bd48b936987f1be4350a8f359b1a76550 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 11 May 2020 11:25:27 +0200 Subject: [PATCH 078/148] fixes #97431 --- .../contrib/debug/browser/breakpointsView.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 4e8fb70e974..8a691c2ccb1 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -61,6 +61,7 @@ export class BreakpointsView extends ViewPane { private list!: WorkbenchList; private needsRefresh = false; + private ignoreLayout = false; constructor( options: IViewletViewOptions, @@ -79,7 +80,6 @@ export class BreakpointsView extends ViewPane { ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this.updateSize(); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); } @@ -164,10 +164,20 @@ export class BreakpointsView extends ViewPane { } protected layoutBody(height: number, width: number): void { + if (this.ignoreLayout) { + return; + } + super.layoutBody(height, width); if (this.list) { this.list.layout(height, width); } + try { + this.ignoreLayout = true; + this.updateSize(); + } finally { + this.ignoreLayout = false; + } } private onListContextMenu(e: IListContextMenuEvent): void { From 136bbd3b3b1924ab5ced5cb8a78a9a854298aef8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 6 May 2020 18:25:30 +0200 Subject: [PATCH 079/148] rework gotoError logic to be fit for #96708 --- src/vs/editor/contrib/gotoError/gotoError.ts | 404 ++++-------------- .../contrib/gotoError/gotoErrorWidget.ts | 6 +- .../gotoError/markerNavigationService.ts | 181 ++++++++ .../gotoError/media/gotoErrorWidget.css | 2 +- .../editor/contrib/hover/modesContentHover.ts | 3 +- 5 files changed, 277 insertions(+), 319 deletions(-) create mode 100644 src/vs/editor/contrib/gotoError/markerNavigationService.ts diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index c785054b50d..7e4823f056f 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Emitter } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { IMarker } from 'vs/platform/markers/common/markers'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; @@ -18,194 +17,32 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { MarkerNavigationWidget } from './gotoErrorWidget'; -import { compare } from 'vs/base/common/strings'; -import { binarySearch, find } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { isEqual } from 'vs/base/common/resources'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { Codicon, registerIcon } from 'vs/base/common/codicons'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; - -class MarkerModel { - - private readonly _editor: ICodeEditor; - private _markers: IMarker[]; - private _nextIdx: number; - private readonly _toUnbind = new DisposableStore(); - private _ignoreSelectionChange: boolean; - private readonly _onCurrentMarkerChanged: Emitter; - private readonly _onMarkerSetChanged: Emitter; - - constructor(editor: ICodeEditor, markers: IMarker[]) { - this._editor = editor; - this._markers = []; - this._nextIdx = -1; - this._ignoreSelectionChange = false; - this._onCurrentMarkerChanged = new Emitter(); - this._onMarkerSetChanged = new Emitter(); - this.setMarkers(markers); - - // listen on editor - this._toUnbind.add(this._editor.onDidDispose(() => this.dispose())); - this._toUnbind.add(this._editor.onDidChangeCursorPosition(() => { - if (this._ignoreSelectionChange) { - return; - } - if (this.currentMarker && this._editor.getPosition() && Range.containsPosition(this.currentMarker, this._editor.getPosition()!)) { - return; - } - this._nextIdx = -1; - })); - } - - public get onCurrentMarkerChanged() { - return this._onCurrentMarkerChanged.event; - } - - public get onMarkerSetChanged() { - return this._onMarkerSetChanged.event; - } - - public setMarkers(markers: IMarker[]): void { - - let oldMarker = this._nextIdx >= 0 ? this._markers[this._nextIdx] : undefined; - this._markers = markers || []; - this._markers.sort(MarkerNavigationAction.compareMarker); - if (!oldMarker) { - this._nextIdx = -1; - } else { - this._nextIdx = Math.max(-1, binarySearch(this._markers, oldMarker, MarkerNavigationAction.compareMarker)); - } - this._onMarkerSetChanged.fire(this); - } - - public withoutWatchingEditorPosition(callback: () => void): void { - this._ignoreSelectionChange = true; - try { - callback(); - } finally { - this._ignoreSelectionChange = false; - } - } - - private _initIdx(fwd: boolean): void { - let found = false; - const position = this._editor.getPosition(); - for (let i = 0; i < this._markers.length; i++) { - let range = Range.lift(this._markers[i]); - - if (range.isEmpty() && this._editor.getModel()) { - const word = this._editor.getModel()!.getWordAtPosition(range.getStartPosition()); - if (word) { - range = new Range(range.startLineNumber, word.startColumn, range.startLineNumber, word.endColumn); - } - } - - if (position && (range.containsPosition(position) || position.isBeforeOrEqual(range.getStartPosition()))) { - this._nextIdx = i; - found = true; - break; - } - } - if (!found) { - // after the last change - this._nextIdx = fwd ? 0 : this._markers.length - 1; - } - if (this._nextIdx < 0) { - this._nextIdx = this._markers.length - 1; - } - } - - get currentMarker(): IMarker | undefined { - return this.canNavigate() ? this._markers[this._nextIdx] : undefined; - } - - set currentMarker(marker: IMarker | undefined) { - const idx = this._nextIdx; - this._nextIdx = -1; - if (marker) { - this._nextIdx = this.indexOf(marker); - } - if (this._nextIdx !== idx) { - this._onCurrentMarkerChanged.fire(marker); - } - } - - public move(fwd: boolean, inCircles: boolean): boolean { - if (!this.canNavigate()) { - this._onCurrentMarkerChanged.fire(undefined); - return !inCircles; - } - - let oldIdx = this._nextIdx; - let atEdge = false; - - if (this._nextIdx === -1) { - this._initIdx(fwd); - - } else if (fwd) { - if (inCircles || this._nextIdx + 1 < this._markers.length) { - this._nextIdx = (this._nextIdx + 1) % this._markers.length; - } else { - atEdge = true; - } - - } else if (!fwd) { - if (inCircles || this._nextIdx > 0) { - this._nextIdx = (this._nextIdx - 1 + this._markers.length) % this._markers.length; - } else { - atEdge = true; - } - } - - if (oldIdx !== this._nextIdx) { - const marker = this._markers[this._nextIdx]; - this._onCurrentMarkerChanged.fire(marker); - } - - return atEdge; - } - - public canNavigate(): boolean { - return this._markers.length > 0; - } - - public findMarkerAtPosition(pos: Position): IMarker | undefined { - return find(this._markers, marker => Range.containsPosition(marker, pos)); - } - - public get total() { - return this._markers.length; - } - - public indexOf(marker: IMarker): number { - return 1 + this._markers.indexOf(marker); - } - - public dispose(): void { - this._toUnbind.dispose(); - } -} +import { IMarkerNavigationService, MarkerList } from 'vs/editor/contrib/gotoError/markerNavigationService'; export class MarkerController implements IEditorContribution { - public static readonly ID = 'editor.contrib.markerController'; + static readonly ID = 'editor.contrib.markerController'; - public static get(editor: ICodeEditor): MarkerController { + static get(editor: ICodeEditor): MarkerController { return editor.getContribution(MarkerController.ID); } private readonly _editor: ICodeEditor; - private _model: MarkerModel | null = null; - private _widget: MarkerNavigationWidget | null = null; + private readonly _widgetVisible: IContextKey; - private readonly _disposeOnClose = new DisposableStore(); + private readonly _sessionDispoables = new DisposableStore(); + + private _model?: MarkerList; + private _widget?: MarkerNavigationWidget; constructor( editor: ICodeEditor, - @IMarkerService private readonly _markerService: IMarkerService, + @IMarkerNavigationService private readonly _markerNavigationService: IMarkerNavigationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ICodeEditorService private readonly _editorService: ICodeEditorService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -214,195 +51,134 @@ export class MarkerController implements IEditorContribution { this._widgetVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService); } - public dispose(): void { + dispose(): void { this._cleanUp(); - this._disposeOnClose.dispose(); + this._sessionDispoables.dispose(); } private _cleanUp(): void { this._widgetVisible.reset(); - this._disposeOnClose.clear(); - this._widget = null; - this._model = null; + this._sessionDispoables.clear(); + this._widget = undefined; + this._model = undefined; } - public getOrCreateModel(): MarkerModel { + private _getOrCreateModel(uri: URI | undefined): MarkerList { - if (this._model) { + if (this._model && this._model.matches(uri)) { return this._model; } + let reusePosition = false; + if (this._model) { + reusePosition = true; + this._cleanUp(); + } - const markers = this._getMarkers(); - this._model = new MarkerModel(this._editor, markers); - this._markerService.onMarkerChanged(this._onMarkerChanged, this, this._disposeOnClose); + this._model = this._markerNavigationService.getMarkerList(uri); + if (reusePosition) { + this._model.move(true, this._editor.getModel()!, this._editor.getPosition()!); + } this._widget = this._instantiationService.createInstance(MarkerNavigationWidget, this._editor); + this._widget.onDidClose(() => this.close(), this, this._sessionDispoables); this._widgetVisible.set(true); - this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose); - this._disposeOnClose.add(this._model); - this._disposeOnClose.add(this._widget); + this._sessionDispoables.add(this._model); + this._sessionDispoables.add(this._widget); - this._disposeOnClose.add(this._widget.onDidSelectRelatedInformation(related => { - this._editorService.openCodeEditor({ - resource: related.resource, - options: { pinned: true, revealIfOpened: true, selection: Range.lift(related).collapseToStart() } - }, this._editor).then(undefined, onUnexpectedError); - this.closeMarkersNavigation(false); - })); - this._disposeOnClose.add(this._editor.onDidChangeModel(() => this._cleanUp())); - - this._disposeOnClose.add(this._model.onCurrentMarkerChanged(marker => { - if (!marker || !this._model) { - this._cleanUp(); - } else { - this._model.withoutWatchingEditorPosition(() => { - if (!this._widget || !this._model) { - return; - } - this._widget.showAtMarker(marker, this._model.indexOf(marker), this._model.total); - }); + // follow cursor + this._sessionDispoables.add(this._editor.onDidChangeCursorPosition(e => { + if (!this._model?.selected || !Range.containsPosition(this._model?.selected.marker, e.position)) { + this._model?.resetIndex(); } })); - this._disposeOnClose.add(this._model.onMarkerSetChanged(() => { + + // update markers + this._sessionDispoables.add(this._model.onDidChange(() => { if (!this._widget || !this._widget.position || !this._model) { return; } - - const marker = this._model.findMarkerAtPosition(this._widget.position); - if (marker) { - this._widget.updateMarker(marker); + const info = this._model.find(this._editor.getModel()!.uri, this._widget!.position!); + if (info) { + this._widget.updateMarker(info.marker); } else { this._widget.showStale(); } })); + // open related + this._sessionDispoables.add(this._widget.onDidSelectRelatedInformation(related => { + this._editorService.openCodeEditor({ + resource: related.resource, + options: { pinned: true, revealIfOpened: true, selection: Range.lift(related).collapseToStart() } + }, this._editor); + this.close(false); + })); + this._sessionDispoables.add(this._editor.onDidChangeModel(() => this._cleanUp())); + return this._model; } - public closeMarkersNavigation(focusEditor: boolean = true): void { + close(focusEditor: boolean = true): void { this._cleanUp(); if (focusEditor) { this._editor.focus(); } } - public show(marker: IMarker): void { - const model = this.getOrCreateModel(); - model.currentMarker = marker; + showAtMarker(marker: IMarker): void { + if (this._editor.hasModel()) { + const model = this._getOrCreateModel(this._editor.getModel().uri); + model.resetIndex(); + model.move(true, this._editor.getModel(), new Position(marker.startLineNumber, marker.startColumn)); + if (model.selected) { + this._widget!.showAtMarker(model.selected.marker, model.selected.index, model.selected.total); + } + } } - private _onMarkerChanged(changedResources: readonly URI[]): void { - const editorModel = this._editor.getModel(); - if (!editorModel) { - return; - } + async nagivate(next: boolean, multiFile: boolean) { + if (this._editor.hasModel()) { + const model = this._getOrCreateModel(multiFile ? undefined : this._editor.getModel().uri); + model.move(next, this._editor.getModel(), this._editor.getPosition()); + if (!model.selected) { + return; + } + if (model.selected.marker.resource.toString() !== this._editor.getModel().uri.toString()) { + // show in different editor + this._cleanUp(); + const otherEditor = await this._editorService.openCodeEditor({ + resource: model.selected.marker.resource, + options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.NearTop, selection: model.selected.marker } + }, this._editor); - if (!this._model) { - return; - } + if (otherEditor) { + MarkerController.get(otherEditor).close(); + MarkerController.get(otherEditor).nagivate(next, multiFile); + } - if (!changedResources.some(r => isEqual(editorModel.uri, r))) { - return; + } else { + // show in this editor + this._widget!.showAtMarker(model.selected.marker, model.selected.index, model.selected.total); + } } - this._model.setMarkers(this._getMarkers()); - } - - private _getMarkers(): IMarker[] { - let model = this._editor.getModel(); - if (!model) { - return []; - } - - return this._markerService.read({ - resource: model.uri, - severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info - }); } } class MarkerNavigationAction extends EditorAction { - private readonly _isNext: boolean; - - private readonly _multiFile: boolean; - - constructor(next: boolean, multiFile: boolean, opts: IActionOptions) { + constructor( + private readonly _next: boolean, + private readonly _multiFile: boolean, + opts: IActionOptions + ) { super(opts); - this._isNext = next; - this._multiFile = multiFile; } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - - const markerService = accessor.get(IMarkerService); - const editorService = accessor.get(ICodeEditorService); - const controller = MarkerController.get(editor); - if (!controller) { - return Promise.resolve(undefined); + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + if (editor.hasModel()) { + MarkerController.get(editor).nagivate(this._next, this._multiFile); } - - const model = controller.getOrCreateModel(); - const atEdge = model.move(this._isNext, !this._multiFile); - if (!atEdge || !this._multiFile) { - return Promise.resolve(undefined); - } - - // try with the next/prev file - let markers = markerService.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }).sort(MarkerNavigationAction.compareMarker); - if (markers.length === 0) { - return Promise.resolve(undefined); - } - - const editorModel = editor.getModel(); - if (!editorModel) { - return Promise.resolve(undefined); - } - - let oldMarker = model.currentMarker || { resource: editorModel!.uri, severity: MarkerSeverity.Error, startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }; - let idx = binarySearch(markers, oldMarker, MarkerNavigationAction.compareMarker); - if (idx < 0) { - // find best match... - idx = ~idx; - idx %= markers.length; - } else if (this._isNext) { - idx = (idx + 1) % markers.length; - } else { - idx = (idx + markers.length - 1) % markers.length; - } - - let newMarker = markers[idx]; - if (isEqual(newMarker.resource, editorModel.uri)) { - // the next `resource` is this resource which - // means we cycle within this file - model.move(this._isNext, true); - return Promise.resolve(undefined); - } - - // close the widget for this editor-instance, open the resource - // for the next marker and re-start marker navigation in there - controller.closeMarkersNavigation(); - - return editorService.openCodeEditor({ - resource: newMarker.resource, - options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.NearTop, selection: newMarker } - }, editor).then(editor => { - if (!editor) { - return undefined; - } - return editor.getAction(this.id).run(); - }); - } - - static compareMarker(a: IMarker, b: IMarker): number { - let res = compare(a.resource.toString(), b.resource.toString()); - if (res === 0) { - res = MarkerSeverity.compare(a.severity, b.severity); - } - if (res === 0) { - res = Range.compareRangesUsingStarts(a, b); - } - return res; } } @@ -501,7 +277,7 @@ const MarkerCommand = EditorCommand.bindToContribution(MarkerC registerEditorCommand(new MarkerCommand({ id: 'closeMarkersNavigation', precondition: CONTEXT_MARKERS_NAVIGATION_VISIBLE, - handler: x => x.closeMarkersNavigation(), + handler: x => x.close(), kbOpts: { weight: KeybindingWeight.EditorContrib + 50, kbExpr: EditorContextKeys.focus, diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 76ffd641d15..7c2eed05550 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -8,7 +8,6 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; -import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerColor, oneOf, textLinkForeground, editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -296,6 +295,9 @@ export class MarkerNavigationWidget extends PeekViewWidget { protected _fillHead(container: HTMLElement): void { super._fillHead(container); + + this._disposables.add(this._actionbarWidget!.actionRunner.onDidBeforeRun(e => this.editor.focus())); + const actions: IAction[] = []; const menu = this._menuService.createMenu(MarkerNavigationWidget.TitleMenu, this._contextKeyService); createAndFillInActionBarActions(menu, undefined, actions); @@ -327,7 +329,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._disposables.add(this._message); } - show(where: Position, heightInLines: number): void { + show(): void { throw new Error('call showAtMarker'); } diff --git a/src/vs/editor/contrib/gotoError/markerNavigationService.ts b/src/vs/editor/contrib/gotoError/markerNavigationService.ts new file mode 100644 index 00000000000..2c6d2d05aa4 --- /dev/null +++ b/src/vs/editor/contrib/gotoError/markerNavigationService.ts @@ -0,0 +1,181 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers'; +import { URI } from 'vs/base/common/uri'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { compare } from 'vs/base/common/strings'; +import { binarySearch } from 'vs/base/common/arrays'; +import { ITextModel } from 'vs/editor/common/model'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class MarkerCoordinate { + constructor( + readonly marker: IMarker, + readonly index: number, + readonly total: number + ) { } +} + +export class MarkerList { + + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + private readonly _dispoables = new DisposableStore(); + + private _markers: IMarker[] = []; + private _nextIdx: number = -1; + + constructor( + private readonly _scope: URI | undefined, + @IMarkerService private readonly _markerService: IMarkerService, + ) { + + const filter = { resource: this._scope, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }; + this._markers = this._markerService.read(filter).sort(MarkerList._compareMarker); + + this._dispoables.add(_markerService.onMarkerChanged(e => { + if (!this._scope || e.some(e => e.toString() === _scope?.toString())) { + this._markers = this._markerService.read(filter).sort(MarkerList._compareMarker); + this._nextIdx = -1; + this._onDidChange.fire(); + } + })); + } + + dispose(): void { + this._dispoables.dispose(); + this._onDidChange.dispose(); + } + + matches(uri: URI | undefined) { + if (this._scope === uri) { + return true; + } + if (this._scope && uri && this._scope.toString() === uri.toString()) { + return true; + } + return false; + } + + get selected(): MarkerCoordinate | undefined { + const marker = this._markers[this._nextIdx]; + return marker && new MarkerCoordinate(marker, this._nextIdx + 1, this._markers.length); + } + + private _initIdx(model: ITextModel, position: Position, fwd: boolean): void { + let found = false; + + let idx = this._markers.findIndex(marker => marker.resource.toString() === model.uri.toString()); + if (idx < 0) { + idx = binarySearch(this._markers, { resource: model.uri }, (a, b) => compare(a.resource.toString(), b.resource.toString())); + if (idx < 0) { + idx = ~idx; + } + } + + for (let i = idx; i < this._markers.length; i++) { + let range = Range.lift(this._markers[i]); + + if (range.isEmpty()) { + const word = model.getWordAtPosition(range.getStartPosition()); + if (word) { + range = new Range(range.startLineNumber, word.startColumn, range.startLineNumber, word.endColumn); + } + } + + if (position && (range.containsPosition(position) || position.isBeforeOrEqual(range.getStartPosition()))) { + this._nextIdx = i; + found = true; + break; + } + + if (this._markers[i].resource.toString() !== model.uri.toString()) { + break; + } + } + + if (!found) { + // after the last change + this._nextIdx = fwd ? 0 : this._markers.length - 1; + } + if (this._nextIdx < 0) { + this._nextIdx = this._markers.length - 1; + } + } + + resetIndex() { + this._nextIdx = -1; + } + + move(fwd: boolean, model: ITextModel, position: Position): boolean { + if (this._markers.length === 0) { + return false; + } + + let oldIdx = this._nextIdx; + if (this._nextIdx === -1) { + this._initIdx(model, position, fwd); + } else if (fwd) { + this._nextIdx = (this._nextIdx + 1) % this._markers.length; + } else if (!fwd) { + this._nextIdx = (this._nextIdx - 1 + this._markers.length) % this._markers.length; + } + + if (oldIdx !== this._nextIdx) { + return true; + } + return false; + } + + find(uri: URI, position: Position): MarkerCoordinate | undefined { + let idx = this._markers.findIndex(marker => marker.resource.toString() === uri.toString()); + if (idx < 0) { + return undefined; + } + for (; idx < this._markers.length; idx++) { + if (Range.containsPosition(this._markers[idx], position)) { + return new MarkerCoordinate(this._markers[idx], idx + 1, this._markers.length); + } + } + return undefined; + } + + private static _compareMarker(a: IMarker, b: IMarker): number { + let res = compare(a.resource.toString(), b.resource.toString()); + if (res === 0) { + res = MarkerSeverity.compare(a.severity, b.severity); + } + if (res === 0) { + res = Range.compareRangesUsingStarts(a, b); + } + return res; + } +} + +export const IMarkerNavigationService = createDecorator('IMarkerNavigationService'); + +export interface IMarkerNavigationService { + readonly _serviceBrand: undefined; + getMarkerList(resource: URI | undefined): MarkerList; +} + +class MarkerNavigationService implements IMarkerNavigationService { + + readonly _serviceBrand: undefined; + + constructor(@IMarkerService private readonly _markerService: IMarkerService) { } + + getMarkerList(resource: URI | undefined) { + return new MarkerList(resource, this._markerService); + } +} + +registerSingleton(IMarkerNavigationService, MarkerNavigationService, true); diff --git a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css index c748c365bdc..bf9d507df0a 100644 --- a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css +++ b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css @@ -32,7 +32,7 @@ user-select: text; -webkit-user-select: text; -ms-user-select: text; - padding: 8px 12px 0px 20px; + padding: 8px 12px 0 20px; } .monaco-editor .marker-widget .descriptioncontainer .message { diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 27e5d9b096d..71055810784 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -567,7 +567,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { commandId: NextMarkerAction.ID, run: () => { this.hide(); - MarkerController.get(this._editor).show(markerHover.marker); + MarkerController.get(this._editor).showAtMarker(markerHover.marker); this._editor.focus(); } })); @@ -686,4 +686,3 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor-hover .hover-contents a.code-link span:hover { color: ${linkFg}; }`); } }); - From 337c788e89b157b64e51b38f62e5d751e6075282 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 May 2020 10:37:38 +0200 Subject: [PATCH 080/148] add marker list provider, #96708 --- .../gotoError/markerNavigationService.ts | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/gotoError/markerNavigationService.ts b/src/vs/editor/contrib/gotoError/markerNavigationService.ts index 2c6d2d05aa4..57869dc4f69 100644 --- a/src/vs/editor/contrib/gotoError/markerNavigationService.ts +++ b/src/vs/editor/contrib/gotoError/markerNavigationService.ts @@ -6,7 +6,7 @@ import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers'; import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { compare } from 'vs/base/common/strings'; @@ -14,6 +14,7 @@ import { binarySearch } from 'vs/base/common/arrays'; import { ITextModel } from 'vs/editor/common/model'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { LinkedList } from 'vs/base/common/linkedList'; export class MarkerCoordinate { constructor( @@ -164,16 +165,35 @@ export const IMarkerNavigationService = createDecorator(); + constructor(@IMarkerService private readonly _markerService: IMarkerService) { } - getMarkerList(resource: URI | undefined) { + registerProvider(provider: IMarkerListProvider): IDisposable { + const remove = this._provider.unshift(provider); + return toDisposable(() => remove()); + } + + getMarkerList(resource: URI | undefined): MarkerList { + for (let provider of this._provider) { + const result = provider.getMarkerList(resource); + if (result) { + return result; + } + } + // default return new MarkerList(resource, this._markerService); } } From 2b472d1e4aaaecdda5ca96289869f90ae82de3a1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 May 2020 11:19:56 +0200 Subject: [PATCH 081/148] register a marker list provider for notebooks, #96708 --- .../gotoError/markerNavigationService.ts | 35 +++++++++----- .../browser/contrib/marker/markerProvider.ts | 47 +++++++++++++++++++ .../notebook/browser/notebook.contribution.ts | 1 + 3 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts diff --git a/src/vs/editor/contrib/gotoError/markerNavigationService.ts b/src/vs/editor/contrib/gotoError/markerNavigationService.ts index 57869dc4f69..11cac8ecd7a 100644 --- a/src/vs/editor/contrib/gotoError/markerNavigationService.ts +++ b/src/vs/editor/contrib/gotoError/markerNavigationService.ts @@ -29,22 +29,38 @@ export class MarkerList { private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; + private readonly _resourceFilter?: (uri: URI) => boolean; private readonly _dispoables = new DisposableStore(); private _markers: IMarker[] = []; private _nextIdx: number = -1; constructor( - private readonly _scope: URI | undefined, + resourceFilter: URI | ((uri: URI) => boolean) | undefined, @IMarkerService private readonly _markerService: IMarkerService, ) { + if (URI.isUri(resourceFilter)) { + this._resourceFilter = uri => uri.toString() === resourceFilter.toString(); + } else if (resourceFilter) { + this._resourceFilter = resourceFilter; + } - const filter = { resource: this._scope, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }; - this._markers = this._markerService.read(filter).sort(MarkerList._compareMarker); + const updateMarker = () => { + this._markers = this._markerService.read({ + resource: URI.isUri(resourceFilter) ? resourceFilter : undefined, + severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info + }); + if (typeof resourceFilter === 'function') { + this._markers = this._markers.filter(m => this._resourceFilter!(m.resource)); + } + this._markers.sort(MarkerList._compareMarker); + }; - this._dispoables.add(_markerService.onMarkerChanged(e => { - if (!this._scope || e.some(e => e.toString() === _scope?.toString())) { - this._markers = this._markerService.read(filter).sort(MarkerList._compareMarker); + updateMarker(); + + this._dispoables.add(_markerService.onMarkerChanged(uris => { + if (!this._resourceFilter || uris.some(uri => this._resourceFilter!(uri))) { + updateMarker(); this._nextIdx = -1; this._onDidChange.fire(); } @@ -57,13 +73,10 @@ export class MarkerList { } matches(uri: URI | undefined) { - if (this._scope === uri) { + if (!this._resourceFilter) { return true; } - if (this._scope && uri && this._scope.toString() === uri.toString()) { - return true; - } - return false; + return uri && this._resourceFilter(uri); } get selected(): MarkerCoordinate | undefined { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts b/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts new file mode 100644 index 00000000000..6d7d9c8a64f --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IMarkerListProvider, MarkerList, IMarkerNavigationService } from 'vs/editor/contrib/gotoError/markerNavigationService'; +import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +class MarkerListProvider implements IMarkerListProvider { + + private readonly _dispoables: IDisposable; + + constructor( + @IMarkerService private readonly _markerService: IMarkerService, + @IMarkerNavigationService markerNavigation: IMarkerNavigationService, + ) { + this._dispoables = markerNavigation.registerProvider(this); + } + + dispose() { + this._dispoables.dispose(); + } + + getMarkerList(resource: URI | undefined): MarkerList | undefined { + if (!resource) { + return undefined; + } + const data = CellUri.parse(resource); + if (!data) { + return undefined; + } + return new MarkerList(uri => { + const otherData = CellUri.parse(uri); + return otherData?.notebook.toString() === data.notebook.toString(); + }, this._markerService); + } +} + +Registry + .as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(MarkerListProvider, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index a726ac0d66a..eb035c05aca 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -44,6 +44,7 @@ import 'vs/workbench/contrib/notebook/browser/contrib/find/findController'; import 'vs/workbench/contrib/notebook/browser/contrib/fold/folding'; import 'vs/workbench/contrib/notebook/browser/contrib/format/formatting'; import 'vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider'; +import 'vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider'; // Output renderers registration From 480272dc7989404049f7bb2d3a4d30313f3d6822 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Mon, 11 May 2020 11:28:20 +0200 Subject: [PATCH 082/148] Add map state test to forEach --- src/vs/base/common/map.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 962df44c0de..8df6ef68bea 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -580,7 +580,9 @@ export const enum Touch { AsNew = 2 } -export class LinkedMap { +export class LinkedMap implements Map { + + readonly [Symbol.toStringTag] = 'LinkedMap'; private _map: Map>; private _head: Item | undefined; @@ -636,7 +638,7 @@ export class LinkedMap { return item.value; } - set(key: K, value: V, touch: Touch = Touch.None): void { + set(key: K, value: V, touch: Touch = Touch.None): this { let item = this._map.get(key); if (item) { item.value = value; @@ -662,6 +664,7 @@ export class LinkedMap { this._map.set(key, item); this._size++; } + return this; } delete(key: K): boolean { @@ -694,6 +697,7 @@ export class LinkedMap { } forEach(callbackfn: (value: V, key: K, map: LinkedMap) => void, thisArg?: any): void { + const state = this._state; let current = this._head; while (current) { if (thisArg) { @@ -701,6 +705,9 @@ export class LinkedMap { } else { callbackfn(current.value, current.key, this); } + if (this._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } current = current.next; } } @@ -987,9 +994,10 @@ export class LRUCache extends LinkedMap { return super.get(key, Touch.None); } - set(key: K, value: V): void { + set(key: K, value: V): this { super.set(key, value, Touch.AsNew); this.checkTrim(); + return this; } private checkTrim() { From 99de27bd7cda217e5f954d7568267606ccff6ce3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 May 2020 11:30:40 +0200 Subject: [PATCH 083/148] :lipstick: simpler menu registration --- src/vs/editor/contrib/gotoError/gotoError.ts | 33 ++++++++------------ 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 7e4823f056f..001ed3f273b 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -18,7 +18,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { MarkerNavigationWidget } from './gotoErrorWidget'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuId } from 'vs/platform/actions/common/actions'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { Codicon, registerIcon } from 'vs/base/common/codicons'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -243,6 +243,12 @@ class NextMarkerInFilesAction extends MarkerNavigationAction { kbExpr: EditorContextKeys.focus, primary: KeyCode.F8, weight: KeybindingWeight.EditorContrib + }, + menuOpts: { + menuId: MenuId.MenubarGoMenu, + title: nls.localize({ key: 'miGotoNextProblem', comment: ['&& denotes a mnemonic'] }, "Next &&Problem"), + group: '6_problem_nav', + order: 1 } }); } @@ -259,6 +265,12 @@ class PrevMarkerInFilesAction extends MarkerNavigationAction { kbExpr: EditorContextKeys.focus, primary: KeyMod.Shift | KeyCode.F8, weight: KeybindingWeight.EditorContrib + }, + menuOpts: { + menuId: MenuId.MenubarGoMenu, + title: nls.localize({ key: 'miGotoPreviousProblem', comment: ['&& denotes a mnemonic'] }, "Previous &&Problem"), + group: '6_problem_nav', + order: 2 } }); } @@ -285,22 +297,3 @@ registerEditorCommand(new MarkerCommand({ secondary: [KeyMod.Shift | KeyCode.Escape] } })); - -// Go to menu -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '6_problem_nav', - command: { - id: 'editor.action.marker.nextInFiles', - title: nls.localize({ key: 'miGotoNextProblem', comment: ['&& denotes a mnemonic'] }, "Next &&Problem") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '6_problem_nav', - command: { - id: 'editor.action.marker.prevInFiles', - title: nls.localize({ key: 'miGotoPreviousProblem', comment: ['&& denotes a mnemonic'] }, "Previous &&Problem") - }, - order: 2 -}); From c1dce208791cfee08904d806fc47d37f5ff2a360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 11 May 2020 12:05:49 +0200 Subject: [PATCH 084/148] fixes #97059 --- src/vs/workbench/contrib/scm/browser/mainPane.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index efac402cf47..3eb3627812f 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -192,6 +192,7 @@ export class MainPane extends ViewPane { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, + @ICommandService private readonly commandService: ICommandService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } @@ -287,6 +288,12 @@ export class MainPane extends ViewPane { menu.dispose(); contextKeyService.dispose(); + if (repository.provider.rootUri) { + secondary.push(new Action('_openInTerminal', localize('open in terminal', "Open In Terminal"), undefined, true, async () => { + await this.commandService.executeCommand('openInTerminal', repository.provider.rootUri); + })); + } + if (secondary.length === 0) { return; } From 6c39ddaea0dc34eb1bf281962582755f32e667e4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 11 May 2020 12:20:25 +0200 Subject: [PATCH 085/148] Add settings for SCM input font family (#97139) * Add settings for input message font family --- .../contrib/scm/browser/repositoryPane.ts | 21 +++++++++++++++++-- .../contrib/scm/browser/scm.contribution.ts | 5 +++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 0ca05abcd97..b1ccaf25ff3 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -638,6 +638,7 @@ export class ToggleViewModeAction extends Action { } export class RepositoryPane extends ViewPane { + private readonly defaultInputFontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif'; private cachedHeight: number | undefined = undefined; private cachedWidth: number | undefined = undefined; @@ -766,13 +767,12 @@ export class RepositoryPane extends ViewPane { cursorWidth: 1, fontSize: 13, lineHeight: 20, - fontFamily: ' system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', + fontFamily: this.getInputEditorFontFamily(), wrappingStrategy: 'advanced', wrappingIndent: 'none', padding: { top: 3, bottom: 3 }, quickSuggestions: false }; - const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { isSimpleWidget: true, contributions: EditorExtensionsRegistry.getSomeEditorContributions([ @@ -796,6 +796,9 @@ export class RepositoryPane extends ViewPane { this._register(this.inputEditor.onDidFocusEditorText(() => addClass(editorContainer, 'synthetic-focus'))); this._register(this.inputEditor.onDidBlurEditorText(() => removeClass(editorContainer, 'synthetic-focus'))); + const onInputFontFamilyChanged = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.inputFontFamily')); + this._register(onInputFontFamilyChanged(() => this.inputEditor.updateOptions({ fontFamily: this.getInputEditorFontFamily() }))); + let query: string | undefined; if (this.repository.provider.rootUri) { @@ -1114,6 +1117,20 @@ export class RepositoryPane extends ViewPane { this.layoutBody(this.cachedHeight); } } + + private getInputEditorFontFamily(): string { + const inputFontFamily = this.configurationService.getValue('scm.inputFontFamily'); + + if (inputFontFamily.toLowerCase() === 'inherit') { + return this.configurationService.getValue('editor.fontFamily'); + } + + if (inputFontFamily.length !== 0 && inputFontFamily.toLowerCase() !== 'default') { + return inputFontFamily; + } + + return this.defaultInputFontFamily; + } } export class RepositoryViewDescriptor implements IViewDescriptor { diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index ccbae2dad6c..83f8b7cb22f 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -150,6 +150,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis description: localize('autoReveal', "Controls whether the SCM view should automatically reveal and select files when opening them."), default: true }, + 'scm.inputFontFamily': { + type: 'string', + description: localize('inputFontFamily', "Controls the font for the input message."), + default: 'default' + } } }); From 4f38898bd1b8004715f460e698c950502e37748e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 May 2020 12:37:07 +0200 Subject: [PATCH 086/148] notebook - show first non-empty line for code cells, select and focus cells on select --- .../notebook/browser/contrib/toc/tocProvider.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts b/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts index 838ffdd7926..81501eda2c5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts @@ -16,16 +16,21 @@ TableOfContentsProviderRegistry.register(NotebookEditor.ID, new class implements // return an entry per markdown header const result: ITableOfContentsEntry[] = []; for (let cell of editor.viewModel.viewCells) { - if (cell.cellKind === CellKind.Code) { - continue; - } const content = cell.getText(); - const matches = content.match(/^[ \t]*(\#+)(.+)$/gm); + const regexp = cell.cellKind === CellKind.Markdown + ? /^[ \t]*(\#+)(.+)$/gm // md: header + : /^.*\w+.*\w*$/m; // code: none empty line + + const matches = content.match(regexp); if (matches && matches.length) { for (let j = 0; j < matches.length; j++) { result.push({ label: matches[j].replace(/^[ \t]*(\#+)/, ''), - reveal: () => editor.revealInCenterIfOutsideViewport(cell) + reveal: () => { + editor.revealInCenterIfOutsideViewport(cell); + editor.selectElement(cell); + editor.focusNotebookCell(cell, 'container'); + } }); } } From 8a48b77b5371c0a113459c4288c4d8fd6a105197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 11 May 2020 13:07:50 +0200 Subject: [PATCH 087/148] fixes #97199 --- src/vs/code/electron-browser/proxy/auth.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/code/electron-browser/proxy/auth.html b/src/vs/code/electron-browser/proxy/auth.html index 7596a20dbd3..f0fc7231e34 100644 --- a/src/vs/code/electron-browser/proxy/auth.html +++ b/src/vs/code/electron-browser/proxy/auth.html @@ -5,7 +5,7 @@ + content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">