From 424b732327c12226b81d6d20eeb7f050a0f3fa6d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 May 2020 19:24:59 +0200 Subject: [PATCH 01/21] bump sync version --- src/vs/platform/userDataSync/common/extensionsSync.ts | 2 +- src/vs/platform/userDataSync/common/globalStateSync.ts | 2 +- src/vs/platform/userDataSync/common/keybindingsSync.ts | 2 +- src/vs/platform/userDataSync/common/settingsSync.ts | 2 +- src/vs/platform/userDataSync/common/snippetsSync.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 38944c6df1d..549e89339c9 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -41,7 +41,7 @@ interface ILastSyncUserData extends IRemoteUserData { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/current.json` }); - protected readonly version: number = 2; + protected readonly version: number = 3; protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } constructor( diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index ec24f757805..28bbbc1a8b5 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -42,7 +42,7 @@ interface ILastSyncUserData extends IRemoteUserData { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/current.json` }); - protected readonly version: number = 1; + protected readonly version: number = 2; constructor( @IFileService fileService: IFileService, diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index da61c3e8931..8140e58c192 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -31,7 +31,7 @@ interface ISyncContent { export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { - protected readonly version: number = 1; + protected readonly version: number = 2; protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'keybindings.json'); protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 810f98dda03..66da466d450 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -35,7 +35,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { _serviceBrand: any; - protected readonly version: number = 1; + protected readonly version: number = 2; protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'settings.json'); protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index 856cf589d82..f129a7a0fb9 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -32,7 +32,7 @@ interface ISinppetsSyncPreviewResult extends ISyncPreviewResult { export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - protected readonly version: number = 1; + protected readonly version: number = 2; private readonly snippetsFolder: URI; private readonly snippetsPreviewFolder: URI; private syncPreviewResultPromise: CancelablePromise | null = null; From 79626343cc02a1cf854320d291f65f6df16359a0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 May 2020 19:50:55 +0200 Subject: [PATCH 02/21] Fix #98389 bump sync version and throw error if sync data cannot be parsed --- .../common/abstractSynchronizer.ts | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 8dd72285963..14d30f7efb4 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -44,6 +44,7 @@ function isSyncData(thing: any): thing is ISyncData { && (thing.version && typeof thing.version === 'number') && (thing.content && typeof thing.content === 'string')) { + // backward compatibility if (Object.keys(thing).length === 2) { return true; } @@ -324,14 +325,13 @@ export abstract class AbstractSynchroniser extends Disposable { if (userData.content === null) { return { ref: parsed.ref, syncData: null } as T; } - let syncData: ISyncData = JSON.parse(userData.content); + const syncData: ISyncData = JSON.parse(userData.content); - // Migration from old content to sync data - if (!isSyncData(syncData)) { - syncData = { version: this.version, content: userData.content }; + /* Check if syncData is of expected type. Return only if matches */ + if (isSyncData(syncData)) { + return { ...parsed, ...{ syncData, content: undefined } }; } - return { ...parsed, ...{ syncData, content: undefined } }; } catch (error) { if (!(error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND)) { // log error always except when file does not exist @@ -355,20 +355,12 @@ export abstract class AbstractSynchroniser extends Disposable { return { ref, syncData }; } - protected parseSyncData(content: string): ISyncData | null { - let syncData: ISyncData | null = null; - try { - syncData = JSON.parse(content); - - // Migration from old content to sync data - if (!isSyncData(syncData)) { - syncData = { version: this.version, content }; - } - - } catch (e) { - this.logService.error(e); + protected parseSyncData(content: string): ISyncData { + const syncData: ISyncData = JSON.parse(content); + if (isSyncData(syncData)) { + return syncData; } - return syncData; + throw new UserDataSyncError(localize('incompatible sync data', "Cannot parse sync data as it is not compatible with current version."), UserDataSyncErrorCode.Incompatible, this.resource); } private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise { From d9f49fabd474c07c248deeeabde7317e8eba0962 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 May 2020 20:07:18 +0200 Subject: [PATCH 03/21] #98389 fix tests --- .../userDataSync/common/abstractSynchronizer.ts | 16 ++++++++++------ .../test/common/synchronizer.test.ts | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 14d30f7efb4..a64d706b304 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -41,8 +41,8 @@ export interface ISyncData { function isSyncData(thing: any): thing is ISyncData { if (thing - && (thing.version && typeof thing.version === 'number') - && (thing.content && typeof thing.content === 'string')) { + && (thing.version !== undefined && typeof thing.version === 'number') + && (thing.content !== undefined && typeof thing.content === 'string')) { // backward compatibility if (Object.keys(thing).length === 2) { @@ -50,7 +50,7 @@ function isSyncData(thing: any): thing is ISyncData { } if (Object.keys(thing).length === 3 - && (thing.machineId && typeof thing.machineId === 'string')) { + && (thing.machineId !== undefined && typeof thing.machineId === 'string')) { return true; } } @@ -356,9 +356,13 @@ export abstract class AbstractSynchroniser extends Disposable { } protected parseSyncData(content: string): ISyncData { - const syncData: ISyncData = JSON.parse(content); - if (isSyncData(syncData)) { - return syncData; + try { + const syncData: ISyncData = JSON.parse(content); + if (isSyncData(syncData)) { + return syncData; + } + } catch (error) { + this.logService.error(error); } throw new UserDataSyncError(localize('incompatible sync data', "Cannot parse sync data as it is not compatible with current version."), UserDataSyncErrorCode.Incompatible, this.resource); } diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 0a2664a4e60..1d01143bd90 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -42,8 +42,8 @@ class TestSynchroniser extends AbstractSynchroniser { protected async performReplace(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { } async apply(ref: string): Promise { - ref = await this.userDataSyncStoreService.write(this.resource, '', ref); - await this.updateLastSyncUserData({ ref, syncData: { content: '', version: this.version } }); + const remoteUserData = await this.updateRemoteUserData('', ref); + await this.updateLastSyncUserData(remoteUserData); } async stop(): Promise { From f887d51fa81d5bda12b7bb4154bfb893e2b10dda Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 May 2020 20:49:37 +0200 Subject: [PATCH 04/21] #98389 Revert setting machineId --- src/vs/platform/userDataSync/common/abstractSynchronizer.ts | 4 ++-- src/vs/platform/userDataSync/common/extensionsSync.ts | 6 +++--- src/vs/platform/userDataSync/common/globalStateSync.ts | 2 +- src/vs/platform/userDataSync/common/keybindingsSync.ts | 2 +- src/vs/platform/userDataSync/common/settingsSync.ts | 2 +- src/vs/platform/userDataSync/common/snippetsSync.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index a64d706b304..a5a3d0a63a7 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -378,8 +378,8 @@ export abstract class AbstractSynchroniser extends Disposable { } protected async updateRemoteUserData(content: string, ref: string | null): Promise { - const machineId = await this.currentMachineIdPromise; - const syncData: ISyncData = { version: this.version, machineId, content }; + await this.currentMachineIdPromise; + const syncData: ISyncData = { version: this.version, content }; ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref); return { ref, syncData }; } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 549e89339c9..dae98944862 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -41,7 +41,7 @@ interface ILastSyncUserData extends IRemoteUserData { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/current.json` }); - protected readonly version: number = 3; + protected readonly version: number = 2; protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } constructor( @@ -355,10 +355,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private parseExtensions(syncData: ISyncData): ISyncExtension[] { let extensions: ISyncExtension[] = JSON.parse(syncData.content); - if (syncData.version !== this.version) { + if (syncData.version === 1) { extensions = extensions.map(e => { // #region Migration from v1 (enabled -> disabled) - if (!(e).enabled) { + if ((e).enabled === false) { e.disabled = true; } delete (e).enabled; diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 28bbbc1a8b5..ec24f757805 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -42,7 +42,7 @@ interface ILastSyncUserData extends IRemoteUserData { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/current.json` }); - protected readonly version: number = 2; + protected readonly version: number = 1; constructor( @IFileService fileService: IFileService, diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 8140e58c192..da61c3e8931 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -31,7 +31,7 @@ interface ISyncContent { export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { - protected readonly version: number = 2; + protected readonly version: number = 1; protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'keybindings.json'); protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 66da466d450..810f98dda03 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -35,7 +35,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { _serviceBrand: any; - protected readonly version: number = 2; + protected readonly version: number = 1; protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'settings.json'); protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index f129a7a0fb9..856cf589d82 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -32,7 +32,7 @@ interface ISinppetsSyncPreviewResult extends ISyncPreviewResult { export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - protected readonly version: number = 2; + protected readonly version: number = 1; private readonly snippetsFolder: URI; private readonly snippetsPreviewFolder: URI; private syncPreviewResultPromise: CancelablePromise | null = null; From f08d0225e0caf6c69c5cc191240667e83c166bdf Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 May 2020 22:31:58 +0200 Subject: [PATCH 05/21] #98389 recover settings --- .../userDataSync/common/settingsSync.ts | 44 +++++++++++++++++++ .../common/userDataSyncService.ts | 7 +++ 2 files changed, 51 insertions(+) diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 810f98dda03..9c156e549b1 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -20,6 +20,8 @@ import { URI } from 'vs/base/common/uri'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { Edit } from 'vs/base/common/jsonFormatter'; +import { setProperty, applyEdits } from 'vs/base/common/jsonEdit'; export interface ISettingsSyncContent { settings: string; @@ -414,4 +416,46 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } } + + async recoverSettings(): Promise { + const fileContent = await this.getLocalFileContent(); + if (!fileContent) { + return; + } + + const syncData: ISyncData = JSON.parse(fileContent.value.toString()); + if (!isSyncData(syncData)) { + return; + } + + const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); + if (!settingsSyncContent) { + return; + } + + let settings = settingsSyncContent.settings; + const formattingOptions = await this.getFormattingOptions(); + for (const key in syncData) { + if (['version', 'content', 'machineId'].indexOf(key) === -1 && (syncData as any)[key] !== undefined) { + const edits: Edit[] = setProperty(settings, [key], (syncData as any)[key], formattingOptions); + if (edits.length) { + settings = applyEdits(settings, edits); + } + } + } + + await this.fileService.writeFile(this.file, VSBuffer.fromString(settings)); + } +} + +function isSyncData(thing: any): thing is ISyncData { + if (thing + && (thing.version !== undefined && typeof thing.version === 'number') + && (thing.content !== undefined && typeof thing.content === 'string') + && (thing.machineId !== undefined && typeof thing.machineId === 'string') + ) { + return true; + } + + return false; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index c7c83a2df8d..caf5355b275 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -118,8 +118,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.updateLastSyncTime(); } + private recoveredSettings: boolean = false; async sync(): Promise { await this.checkEnablement(); + + if (!this.recoveredSettings) { + await this.settingsSynchroniser.recoverSettings(); + this.recoveredSettings = true; + } + await this.syncThrottler.queue(() => this.doSync()); } From 2591ede95a52400d5d22989b16706f9b8b4831da Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 May 2020 22:39:02 +0200 Subject: [PATCH 06/21] #98389 ignore error --- .../userDataSync/common/settingsSync.ts | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 9c156e549b1..47b90123edb 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -418,33 +418,35 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { } async recoverSettings(): Promise { - const fileContent = await this.getLocalFileContent(); - if (!fileContent) { - return; - } + try { + const fileContent = await this.getLocalFileContent(); + if (!fileContent) { + return; + } - const syncData: ISyncData = JSON.parse(fileContent.value.toString()); - if (!isSyncData(syncData)) { - return; - } + const syncData: ISyncData = JSON.parse(fileContent.value.toString()); + if (!isSyncData(syncData)) { + return; + } - const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); - if (!settingsSyncContent) { - return; - } + const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); + if (!settingsSyncContent || !settingsSyncContent.settings) { + return; + } - let settings = settingsSyncContent.settings; - const formattingOptions = await this.getFormattingOptions(); - for (const key in syncData) { - if (['version', 'content', 'machineId'].indexOf(key) === -1 && (syncData as any)[key] !== undefined) { - const edits: Edit[] = setProperty(settings, [key], (syncData as any)[key], formattingOptions); - if (edits.length) { - settings = applyEdits(settings, edits); + let settings = settingsSyncContent.settings; + const formattingOptions = await this.getFormattingOptions(); + for (const key in syncData) { + if (['version', 'content', 'machineId'].indexOf(key) === -1 && (syncData as any)[key] !== undefined) { + const edits: Edit[] = setProperty(settings, [key], (syncData as any)[key], formattingOptions); + if (edits.length) { + settings = applyEdits(settings, edits); + } } } - } - await this.fileService.writeFile(this.file, VSBuffer.fromString(settings)); + await this.fileService.writeFile(this.file, VSBuffer.fromString(settings)); + } catch (e) {/* ignore */ } } } From a1ed386162d6a5741ed440c6b8998ca5728a89f8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 May 2020 23:27:07 +0200 Subject: [PATCH 07/21] add telemetry --- src/vs/platform/userDataSync/common/abstractSynchronizer.ts | 2 +- src/vs/platform/userDataSync/common/settingsSync.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index a5a3d0a63a7..4a92b4241b2 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -89,7 +89,7 @@ export abstract class AbstractSynchroniser extends Disposable { @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService protected readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService protected readonly telemetryService: ITelemetryService, @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, @IConfigurationService protected readonly configurationService: IConfigurationService, ) { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 47b90123edb..3c5d1a61311 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -429,6 +429,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { return; } + this.telemetryService.publicLog2('sync/settingsCorrupted'); const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); if (!settingsSyncContent || !settingsSyncContent.settings) { return; From 6ff4290a190a1f8512beaded0d940e2a147ce8fd Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 20 May 2020 17:05:12 +0200 Subject: [PATCH 08/21] Add ViewModelEventDispatcher --- .../browser/controller/textAreaHandler.ts | 11 +-- src/vs/editor/browser/view/viewImpl.ts | 41 +++----- .../browser/viewParts/minimap/minimap.ts | 9 +- .../browser/viewParts/viewZones/viewZones.ts | 2 - src/vs/editor/common/view/viewContext.ts | 10 +- .../editor/common/view/viewEventDispatcher.ts | 92 ------------------ src/vs/editor/common/view/viewEvents.ts | 53 ++-------- src/vs/editor/common/viewLayout/viewLayout.ts | 3 +- src/vs/editor/common/viewModel/viewModel.ts | 9 +- .../viewModel/viewModelEventDispatcher.ts | 96 +++++++++++++++++++ .../editor/common/viewModel/viewModelImpl.ts | 46 ++++++--- .../common/viewModel/viewModelImpl.test.ts | 10 +- 12 files changed, 175 insertions(+), 207 deletions(-) delete mode 100644 src/vs/editor/common/view/viewEventDispatcher.ts create mode 100644 src/vs/editor/common/viewModel/viewModelEventDispatcher.ts diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index adb33c26620..9eee0dfe476 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -258,14 +258,13 @@ export class TextAreaHandler extends ViewPart { const lineNumber = this._selections[0].startLineNumber; const column = this._selections[0].startColumn - (e.moveOneCharacterLeft ? 1 : 0); - this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( + this._context.model.revealRange( 'keyboard', - new Range(lineNumber, column, lineNumber, column), - null, - viewEvents.VerticalRevealType.Simple, true, + new Range(lineNumber, column, lineNumber, column), + viewEvents.VerticalRevealType.Simple, ScrollType.Immediate - )); + ); // Find range pixel position const visibleRange = this._viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column); @@ -308,12 +307,10 @@ export class TextAreaHandler extends ViewPart { this._register(this._textAreaInput.onFocus(() => { this._context.model.setHasFocus(true); - this._context.privateViewEventBus.emit(new viewEvents.ViewFocusChangedEvent(true)); })); this._register(this._textAreaInput.onBlur(() => { this._context.model.setHasFocus(false); - this._context.privateViewEventBus.emit(new viewEvents.ViewFocusChangedEvent(false)); })); } diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 703cd0afae5..1f2252fe582 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -42,7 +42,6 @@ import { Range } from 'vs/editor/common/core/range'; import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; import { RenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; -import { ViewEventDispatcher } from 'vs/editor/common/view/viewEventDispatcher'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; @@ -64,8 +63,6 @@ export interface IOverlayWidgetData { export class View extends ViewEventHandler { - private readonly eventDispatcher: ViewEventDispatcher; - private _scrollbar: EditorScrollbar; private readonly _context: ViewContext; private _selections: Selection[]; @@ -107,18 +104,15 @@ export class View extends ViewEventHandler { const viewController = new ViewController(configuration, model, this.outgoingEvents, commandDelegate); - // The event dispatcher will always go through _renderOnce before dispatching any events - this.eventDispatcher = new ViewEventDispatcher((callback: () => void) => this._renderOnce(callback)); + // The view context is passed on to most classes (basically to reduce param. counts in ctors) + this._context = new ViewContext(configuration, themeService.getColorTheme(), model); // Ensure the view is the first event handler in order to update the layout - this.eventDispatcher.addEventHandler(this); - - // The view context is passed on to most classes (basically to reduce param. counts in ctors) - this._context = new ViewContext(configuration, themeService.getColorTheme(), model, this.eventDispatcher); + this._context.addEventHandler(this); this._register(themeService.onDidColorThemeChange(theme => { this._context.theme.update(theme); - this.eventDispatcher.emit(new viewEvents.ViewThemeChangedEvent()); + this._context.model.onDidColorThemeChange(); this.render(true, false); })); @@ -224,10 +218,6 @@ export class View extends ViewEventHandler { // Pointer handler this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper())); - - this._register(model.addViewEventListener((events: viewEvents.ViewEvent[]) => { - this.eventDispatcher.emitMany(events); - })); } private _flushAccumulatedAndRenderNow(): void { @@ -300,7 +290,10 @@ export class View extends ViewEventHandler { } // --- begin event handlers - + public handleEvents(events: viewEvents.ViewEvent[]): void { + super.handleEvents(events); + this._scheduleRender(); + } public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { this.domNode.setClassName(this.getEditorClassName()); this._applyLayout(); @@ -340,7 +333,7 @@ export class View extends ViewEventHandler { this._renderAnimationFrame = null; } - this.eventDispatcher.removeEventHandler(this); + this._context.removeEventHandler(this); this.outgoingEvents.dispose(); this.viewLines.dispose(); @@ -354,12 +347,6 @@ export class View extends ViewEventHandler { super.dispose(); } - private _renderOnce(callback: () => T): T { - const r = safeInvokeNoArg(callback); - this._scheduleRender(); - return r; - } - private _scheduleRender(): void { if (this._renderAnimationFrame === null) { this._renderAnimationFrame = dom.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), 100); @@ -477,13 +464,9 @@ export class View extends ViewEventHandler { } public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean { - return this._renderOnce(() => { - const zonesHaveChanged = this.viewZones.changeViewZones(callback); - if (zonesHaveChanged) { - this._context.privateViewEventBus.emit(new viewEvents.ViewZonesChangedEvent()); - } - return zonesHaveChanged; - }); + const hadAChange = this.viewZones.changeViewZones(callback); + this._scheduleRender(); + return hadAChange; } public render(now: boolean, everything: boolean): void { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 585a9fbfcdf..1be53aad7a3 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1011,14 +1011,13 @@ export class Minimap extends ViewPart implements IMinimapModel { if (this._samplingState) { lineNumber = this._samplingState.minimapLines[lineNumber - 1]; } - this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( + this._context.model.revealRange( 'mouse', - new Range(lineNumber, 1, lineNumber, 1), - null, - viewEvents.VerticalRevealType.Center, false, + new Range(lineNumber, 1, lineNumber, 1), + viewEvents.VerticalRevealType.Center, ScrollType.Smooth - )); + ); } public setScrollTop(scrollTop: number): void { diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index 47a2b6d3e8e..a73026a3f9b 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -222,8 +222,6 @@ export class ViewZones extends ViewPart { changeAccessor.addZone = invalidFunc; changeAccessor.removeZone = invalidFunc; changeAccessor.layoutZone = invalidFunc; - - return zonesHaveChanged; }); return zonesHaveChanged; diff --git a/src/vs/editor/common/view/viewContext.ts b/src/vs/editor/common/view/viewContext.ts index 573b0827f0a..74628ebb857 100644 --- a/src/vs/editor/common/view/viewContext.ts +++ b/src/vs/editor/common/view/viewContext.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IConfiguration } from 'vs/editor/common/editorCommon'; -import { ViewEventDispatcher } from 'vs/editor/common/view/viewEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { IViewLayout, IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { IColorTheme, ThemeType } from 'vs/platform/theme/common/themeService'; @@ -37,27 +36,24 @@ export class ViewContext { public readonly configuration: IConfiguration; public readonly model: IViewModel; public readonly viewLayout: IViewLayout; - public readonly privateViewEventBus: ViewEventDispatcher; public readonly theme: EditorTheme; constructor( configuration: IConfiguration, theme: IColorTheme, - model: IViewModel, - privateViewEventBus: ViewEventDispatcher + model: IViewModel ) { this.configuration = configuration; this.theme = new EditorTheme(theme); this.model = model; this.viewLayout = model.viewLayout; - this.privateViewEventBus = privateViewEventBus; } public addEventHandler(eventHandler: ViewEventHandler): void { - this.privateViewEventBus.addEventHandler(eventHandler); + this.model.addViewEventHandler(eventHandler); } public removeEventHandler(eventHandler: ViewEventHandler): void { - this.privateViewEventBus.removeEventHandler(eventHandler); + this.model.removeViewEventHandler(eventHandler); } } diff --git a/src/vs/editor/common/view/viewEventDispatcher.ts b/src/vs/editor/common/view/viewEventDispatcher.ts deleted file mode 100644 index 54bd7a6e986..00000000000 --- a/src/vs/editor/common/view/viewEventDispatcher.ts +++ /dev/null @@ -1,92 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ViewEvent } from 'vs/editor/common/view/viewEvents'; -import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; - -export class ViewEventDispatcher { - - private readonly _eventHandlerGateKeeper: (callback: () => void) => void; - private readonly _eventHandlers: ViewEventHandler[]; - private _eventQueue: ViewEvent[] | null; - private _isConsumingQueue: boolean; - - constructor(eventHandlerGateKeeper: (callback: () => void) => void) { - this._eventHandlerGateKeeper = eventHandlerGateKeeper; - this._eventHandlers = []; - this._eventQueue = null; - this._isConsumingQueue = false; - } - - public addEventHandler(eventHandler: ViewEventHandler): void { - for (let i = 0, len = this._eventHandlers.length; i < len; i++) { - if (this._eventHandlers[i] === eventHandler) { - console.warn('Detected duplicate listener in ViewEventDispatcher', eventHandler); - } - } - this._eventHandlers.push(eventHandler); - } - - public removeEventHandler(eventHandler: ViewEventHandler): void { - for (let i = 0; i < this._eventHandlers.length; i++) { - if (this._eventHandlers[i] === eventHandler) { - this._eventHandlers.splice(i, 1); - break; - } - } - } - - public emit(event: ViewEvent): void { - - if (this._eventQueue) { - this._eventQueue.push(event); - } else { - this._eventQueue = [event]; - } - - if (!this._isConsumingQueue) { - this.consumeQueue(); - } - } - - public emitMany(events: ViewEvent[]): void { - if (this._eventQueue) { - this._eventQueue = this._eventQueue.concat(events); - } else { - this._eventQueue = events; - } - - if (!this._isConsumingQueue) { - this.consumeQueue(); - } - } - - private consumeQueue(): void { - this._eventHandlerGateKeeper(() => { - try { - this._isConsumingQueue = true; - - this._doConsumeQueue(); - - } finally { - this._isConsumingQueue = false; - } - }); - } - - private _doConsumeQueue(): void { - while (this._eventQueue) { - // Empty event queue, as events might come in while sending these off - let events = this._eventQueue; - this._eventQueue = null; - - // Use a clone of the event handlers list, as they might remove themselves - let eventHandlers = this._eventHandlers.slice(0); - for (let i = 0, len = eventHandlers.length; i < len; i++) { - eventHandlers[i].handleEvents(events); - } - } - } -} diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 70d7b0577f4..86645dad366 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as errors from 'vs/base/common/errors'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ViewModelEventDispatcher } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; @@ -330,27 +330,16 @@ export interface IViewEventListener { (events: ViewEvent[]): void; } -export interface IViewEventEmitter { - addViewEventListener(listener: IViewEventListener): IDisposable; -} - -export class ViewEventEmitter extends Disposable implements IViewEventEmitter { - private _listeners: IViewEventListener[]; +export class ViewEventEmitter extends Disposable { private _collector: ViewEventsCollector | null; private _collectorCnt: number; constructor() { super(); - this._listeners = []; this._collector = null; this._collectorCnt = 0; } - public dispose(): void { - this._listeners = []; - super.dispose(); - } - protected _beginEmitViewEvents(): ViewEventsCollector { this._collectorCnt++; if (this._collectorCnt === 1) { @@ -359,45 +348,25 @@ export class ViewEventEmitter extends Disposable implements IViewEventEmitter { return this._collector!; } - protected _endEmitViewEvents(): void { + protected _endEmitViewEvents(eventDispatcher: ViewModelEventDispatcher): void { this._collectorCnt--; if (this._collectorCnt === 0) { const events = this._collector!.finalize(); this._collector = null; if (events.length > 0) { - this._emit(events); + eventDispatcher.emitMany(events); } } } - protected _emitSingleViewEvent(event: ViewEvent): void { + protected _emitSingleViewEvent(eventDispatcher: ViewModelEventDispatcher, event: ViewEvent): void { try { const eventsCollector = this._beginEmitViewEvents(); eventsCollector.emit(event); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(eventDispatcher); } } - - private _emit(events: ViewEvent[]): void { - const listeners = this._listeners.slice(0); - for (let i = 0, len = listeners.length; i < len; i++) { - safeInvokeListener(listeners[i], events); - } - } - - public addViewEventListener(listener: IViewEventListener): IDisposable { - this._listeners.push(listener); - return toDisposable(() => { - let listeners = this._listeners; - for (let i = 0, len = listeners.length; i < len; i++) { - if (listeners[i] === listener) { - listeners.splice(i, 1); - break; - } - } - }); - } } export class ViewEventsCollector { @@ -421,11 +390,3 @@ export class ViewEventsCollector { } } - -function safeInvokeListener(listener: IViewEventListener, events: ViewEvent[]): void { - try { - listener(events); - } catch (e) { - errors.onUnexpectedError(e); - } -} diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index cdb142a4152..961eeb314b1 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -353,11 +353,12 @@ export class ViewLayout extends Disposable implements IViewLayout { } // ---- IVerticalLayoutProvider - public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): void { + public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): boolean { const hadAChange = this._linesLayout.changeWhitespace(callback); if (hadAChange) { this.onHeightMaybeChanged(); } + return hadAChange; } public getVerticalOffsetForLineNumber(lineNumber: number): number { return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber); diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 14d6f7c4a59..91ab8513c4c 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -10,12 +10,13 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { INewScrollPosition, ScrollType } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions, TextModelResolvedOptions, ITextModel } from 'vs/editor/common/model'; -import { IViewEventEmitter, VerticalRevealType } from 'vs/editor/common/view/viewEvents'; +import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; import { ICursorSimpleModel, PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; +import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; export interface IViewWhitespaceViewportData { readonly id: string; @@ -83,7 +84,7 @@ export interface ICoordinatesConverter { modelPositionIsVisible(modelPosition: Position): boolean; } -export interface IViewModel extends IViewEventEmitter, ICursorSimpleModel { +export interface IViewModel extends ICursorSimpleModel { readonly model: ITextModel; @@ -93,12 +94,16 @@ export interface IViewModel extends IViewEventEmitter, ICursorSimpleModel { readonly cursorConfig: CursorConfiguration; + addViewEventHandler(eventHandler: ViewEventHandler): void; + removeViewEventHandler(eventHandler: ViewEventHandler): void; + /** * Gives a hint that a lot of requests are about to come in for these line numbers. */ setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void; tokenizeViewport(): void; setHasFocus(hasFocus: boolean): void; + onDidColorThemeChange(): void; getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; diff --git a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts new file mode 100644 index 00000000000..515dfccc389 --- /dev/null +++ b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; +import { ViewEvent } from 'vs/editor/common/view/viewEvents'; +import { IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; + +export class ViewModelEventDispatcher { + + private readonly _eventHandlers: ViewEventHandler[]; + private _viewEventQueue: ViewEvent[] | null; + private _isConsumingViewEventQueue: boolean; + // private _outgoingContentSizeChangedEvent: OutgoingContentSizeChangedEvent | null; + + constructor() { + this._eventHandlers = []; + this._viewEventQueue = null; + this._isConsumingViewEventQueue = false; + // this._outgoingContentSizeChangedEvent = null; + } + + public addViewEventHandler(eventHandler: ViewEventHandler): void { + for (let i = 0, len = this._eventHandlers.length; i < len; i++) { + if (this._eventHandlers[i] === eventHandler) { + console.warn('Detected duplicate listener in ViewEventDispatcher', eventHandler); + } + } + this._eventHandlers.push(eventHandler); + } + + public removeViewEventHandler(eventHandler: ViewEventHandler): void { + for (let i = 0; i < this._eventHandlers.length; i++) { + if (this._eventHandlers[i] === eventHandler) { + this._eventHandlers.splice(i, 1); + break; + } + } + } + + public emitMany(events: ViewEvent[]): void { + if (this._viewEventQueue) { + this._viewEventQueue = this._viewEventQueue.concat(events); + } else { + this._viewEventQueue = events; + } + + if (!this._isConsumingViewEventQueue) { + this._consumeViewEventQueue(); + } + } + + private _consumeViewEventQueue(): void { + try { + this._isConsumingViewEventQueue = true; + this._doConsumeQueue(); + } finally { + this._isConsumingViewEventQueue = false; + } + } + + private _doConsumeQueue(): void { + while (this._viewEventQueue) { + // Empty event queue, as events might come in while sending these off + const events = this._viewEventQueue; + this._viewEventQueue = null; + + // Use a clone of the event handlers list, as they might remove themselves + const eventHandlers = this._eventHandlers.slice(0); + for (const eventHandler of eventHandlers) { + eventHandler.handleEvents(events); + } + } + } +} + +export class OutgoingContentSizeChangedEvent implements IContentSizeChangedEvent { + + private readonly _oldContentWidth: number; + private readonly _oldContentHeight: number; + + readonly contentWidth: number; + readonly contentHeight: number; + readonly contentWidthChanged: boolean; + readonly contentHeightChanged: boolean; + + constructor(oldContentWidth: number, oldContentHeight: number, contentWidth: number, contentHeight: number) { + this._oldContentWidth = oldContentWidth; + this._oldContentHeight = oldContentHeight; + this.contentWidth = contentWidth; + this.contentHeight = contentHeight; + this.contentWidthChanged = (this._oldContentWidth !== this.contentWidth); + this.contentHeightChanged = (this._oldContentHeight !== this.contentHeight); + } +} diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index cc855612482..655f338c33b 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -29,6 +29,8 @@ import { Cursor } from 'vs/editor/common/controller/cursor'; import { PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; +import { ViewModelEventDispatcher } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -37,6 +39,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel private readonly editorId: number; private readonly configuration: IConfiguration; public readonly model: ITextModel; + private readonly _eventDispatcher: ViewModelEventDispatcher; public cursorConfig: CursorConfiguration; private readonly _tokenizeViewportSoon: RunOnceScheduler; private readonly _updateConfigurationViewLineCount: RunOnceScheduler; @@ -63,6 +66,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.editorId = editorId; this.configuration = configuration; this.model = model; + this._eventDispatcher = new ViewModelEventDispatcher(); this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50)); this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); @@ -104,11 +108,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (e.scrollTopChanged) { this._tokenizeViewportSoon.schedule(); } - this._emitSingleViewEvent(new viewEvents.ViewScrollChangedEvent(e)); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewScrollChangedEvent(e)); })); this._register(this.viewLayout.onDidContentSizeChange((e) => { - this._emitSingleViewEvent(new viewEvents.ViewContentSizeChangedEvent(e)); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewContentSizeChangedEvent(e)); })); this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter); @@ -120,12 +124,12 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const eventsCollector = this._beginEmitViewEvents(); this._onConfigurationChanged(eventsCollector, e); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } })); this._register(MinimapTokensColorTracker.getInstance().onDidChange(() => { - this._emitSingleViewEvent(new viewEvents.ViewTokensColorsChangedEvent()); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewTokensColorsChangedEvent()); })); this._updateConfigurationViewLineCountNow(); @@ -141,6 +145,14 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); } + public addViewEventHandler(eventHandler: ViewEventHandler): void { + this._eventDispatcher.addViewEventHandler(eventHandler); + } + + public removeViewEventHandler(eventHandler: ViewEventHandler): void { + this._eventDispatcher.removeViewEventHandler(eventHandler); + } + private _updateConfigurationViewLineCountNow(): void { this.configuration.setViewLineCount(this.lines.getViewLineCount()); } @@ -155,6 +167,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel public setHasFocus(hasFocus: boolean): void { this.hasFocus = hasFocus; this.cursor.setHasFocus(hasFocus); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewFocusChangedEvent(hasFocus)); + } + + public onDidColorThemeChange(): void { + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewThemeChangedEvent()); } private _onConfigurationChanged(eventsCollector: viewEvents.ViewEventsCollector, e: ConfigurationChangedEvent): void { @@ -308,7 +325,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.decorations.onLineMappingChanged(); } } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } // Update the configuration and reset the centered view line @@ -330,7 +347,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const eventsCollector = this._beginEmitViewEvents(); this.cursor.onModelContentChanged(eventsCollector, e); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } })); @@ -345,7 +362,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel toLineNumber: viewEndLineNumber }; } - this._emitSingleViewEvent(new viewEvents.ViewTokensChangedEvent(viewRanges)); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewTokensChangedEvent(viewRanges)); if (e.tokenizationSupportChanged) { this._tokenizeViewportSoon.schedule(); @@ -353,7 +370,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel })); this._register(this.model.onDidChangeLanguageConfiguration((e) => { - this._emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent()); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewLanguageConfigurationEvent()); this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); this.cursor.updateConfiguration(this.cursorConfig); })); @@ -375,7 +392,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } this._updateConfigurationViewLineCount.schedule(); } @@ -386,7 +403,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this._register(this.model.onDidChangeDecorations((e) => { this.decorations.onModelDecorationsChanged(); - this._emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e)); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewDecorationsChangedEvent(e)); })); } @@ -404,7 +421,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.viewLayout.onHeightMaybeChanged(); } } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } this._updateConfigurationViewLineCount.schedule(); } @@ -951,7 +968,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.viewLayout.deltaScrollNow(deltaScrollLeft, deltaScrollTop); } public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): void { - return this.viewLayout.changeWhitespace(callback); + const hadAChange = this.viewLayout.changeWhitespace(callback); + if (hadAChange) { + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewZonesChangedEvent()); + } } public setMaxLineWidth(maxLineWidth: number): void { this.viewLayout.setMaxLineWidth(maxLineWidth); @@ -963,7 +983,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const eventsCollector = this._beginEmitViewEvents(); callback(eventsCollector); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } } } diff --git a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts index 14110739815..6fe30a1284a 100644 --- a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts @@ -7,6 +7,8 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence } from 'vs/editor/common/model'; import { testViewModel } from 'vs/editor/test/common/viewModel/testViewModel'; +import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; +import { ViewEvent } from 'vs/editor/common/view/viewEvents'; suite('ViewModel', () => { @@ -63,9 +65,11 @@ suite('ViewModel', () => { let viewLineCount: number[] = []; viewLineCount.push(viewModel.getLineCount()); - viewModel.addViewEventListener((events) => { - // Access the view model - viewLineCount.push(viewModel.getLineCount()); + viewModel.addViewEventHandler(new class extends ViewEventHandler { + handleEvents(events: ViewEvent[]): void { + // Access the view model + viewLineCount.push(viewModel.getLineCount()); + } }); model.undo(); viewLineCount.push(viewModel.getLineCount()); From 1620c36f115b469dd57aaedc4a28f95f35ba13a4 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 20 May 2020 23:10:33 +0200 Subject: [PATCH 09/21] Emit the content size changed event through the view model --- src/vs/editor/browser/view/viewImpl.ts | 4 - .../editor/browser/view/viewOutgoingEvents.ts | 9 +- .../editor/browser/widget/codeEditorWidget.ts | 10 +- src/vs/editor/common/view/viewEvents.ts | 101 +++--------------- src/vs/editor/common/viewLayout/viewLayout.ts | 20 ++-- .../common/viewModel/viewEventHandler.ts | 10 +- .../viewModel/viewModelEventDispatcher.ts | 90 ++++++++++++++-- .../editor/common/viewModel/viewModelImpl.ts | 52 ++++----- 8 files changed, 149 insertions(+), 147 deletions(-) diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 1f2252fe582..19eb4fc14a5 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -299,10 +299,6 @@ export class View extends ViewEventHandler { this._applyLayout(); return false; } - public onContentSizeChanged(e: viewEvents.ViewContentSizeChangedEvent): boolean { - this.outgoingEvents.emitContentSizeChange(e); - return false; - } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { this._selections = e.selections; return false; diff --git a/src/vs/editor/browser/view/viewOutgoingEvents.ts b/src/vs/editor/browser/view/viewOutgoingEvents.ts index 3facef38e01..0c6e7c16615 100644 --- a/src/vs/editor/browser/view/viewOutgoingEvents.ts +++ b/src/vs/editor/browser/view/viewOutgoingEvents.ts @@ -9,7 +9,7 @@ import { MouseTarget } from 'vs/editor/browser/controller/mouseTarget'; import { IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IScrollEvent, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; +import { IScrollEvent } from 'vs/editor/common/editorCommon'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -20,7 +20,6 @@ export interface EventCallback { export class ViewOutgoingEvents extends Disposable { - public onDidContentSizeChange: EventCallback | null = null; public onDidScroll: EventCallback | null = null; public onDidGainFocus: EventCallback | null = null; public onDidLoseFocus: EventCallback | null = null; @@ -42,12 +41,6 @@ export class ViewOutgoingEvents extends Disposable { this._viewModel = viewModel; } - public emitContentSizeChange(e: viewEvents.ViewContentSizeChangedEvent): void { - if (this.onDidContentSizeChange) { - this.onDidContentSizeChange(e); - } - } - public emitScrollChanged(e: viewEvents.ViewScrollChangedEvent): void { if (this.onDidScroll) { this.onDidScroll(e); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 0145aeb2280..592dde96f89 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -54,6 +54,7 @@ import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/m import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; import { WordOperations } from 'vs/editor/common/controller/cursorWordOperations'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; +import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; let EDITOR_ID = 0; @@ -1509,6 +1510,14 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._onDidChangeCursorSelection.fire(e2); })); + listenersToRemove.push(viewModel.onEvent((e) => { + switch (e.kind) { + case OutgoingViewModelEventKind.ContentSizeChanged: + this._onDidContentSizeChange.fire(e); + break; + } + })); + const [view, hasRealView] = this._createView(viewModel); if (hasRealView) { this._domElement.appendChild(view.domNode.domNode); @@ -1592,7 +1601,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE }; const viewOutgoingEvents = new ViewOutgoingEvents(viewModel); - viewOutgoingEvents.onDidContentSizeChange = (e) => this._onDidContentSizeChange.fire(e); viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e); viewOutgoingEvents.onDidGainFocus = () => onDidChangeTextFocus(true); viewOutgoingEvents.onDidLoseFocus = () => onDidChangeTextFocus(false); diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 86645dad366..6c613e90418 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -3,33 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ViewModelEventDispatcher } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; -import { Disposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { ScrollType, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; +import { ScrollType } from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents'; export const enum ViewEventType { - ViewConfigurationChanged = 1, - ViewContentSizeChanged = 2, - ViewCursorStateChanged = 3, - ViewDecorationsChanged = 4, - ViewFlushed = 5, - ViewFocusChanged = 6, - ViewLanguageConfigurationChanged = 7, - ViewLineMappingChanged = 8, - ViewLinesChanged = 9, - ViewLinesDeleted = 10, - ViewLinesInserted = 11, - ViewRevealRangeRequest = 12, - ViewScrollChanged = 13, - ViewThemeChanged = 14, - ViewTokensChanged = 15, - ViewTokensColorsChanged = 16, - ViewZonesChanged = 17, + ViewConfigurationChanged, + ViewCursorStateChanged, + ViewDecorationsChanged, + ViewFlushed, + ViewFocusChanged, + ViewLanguageConfigurationChanged, + ViewLineMappingChanged, + ViewLinesChanged, + ViewLinesDeleted, + ViewLinesInserted, + ViewRevealRangeRequest, + ViewScrollChanged, + ViewThemeChanged, + ViewTokensChanged, + ViewTokensColorsChanged, + ViewZonesChanged, } export class ViewConfigurationChangedEvent { @@ -47,25 +44,6 @@ export class ViewConfigurationChangedEvent { } } -export class ViewContentSizeChangedEvent implements IContentSizeChangedEvent { - - public readonly type = ViewEventType.ViewContentSizeChanged; - - public readonly contentWidth: number; - public readonly contentHeight: number; - - public readonly contentWidthChanged: boolean; - public readonly contentHeightChanged: boolean; - - constructor(source: IContentSizeChangedEvent) { - this.contentWidth = source.contentWidth; - this.contentHeight = source.contentHeight; - - this.contentWidthChanged = source.contentWidthChanged; - this.contentHeightChanged = source.contentHeightChanged; - } -} - export class ViewCursorStateChangedEvent { public readonly type = ViewEventType.ViewCursorStateChanged; @@ -308,7 +286,6 @@ export class ViewZonesChangedEvent { export type ViewEvent = ( ViewConfigurationChangedEvent - | ViewContentSizeChangedEvent | ViewCursorStateChangedEvent | ViewDecorationsChangedEvent | ViewFlushedEvent @@ -326,49 +303,6 @@ export type ViewEvent = ( | ViewZonesChangedEvent ); -export interface IViewEventListener { - (events: ViewEvent[]): void; -} - -export class ViewEventEmitter extends Disposable { - private _collector: ViewEventsCollector | null; - private _collectorCnt: number; - - constructor() { - super(); - this._collector = null; - this._collectorCnt = 0; - } - - protected _beginEmitViewEvents(): ViewEventsCollector { - this._collectorCnt++; - if (this._collectorCnt === 1) { - this._collector = new ViewEventsCollector(); - } - return this._collector!; - } - - protected _endEmitViewEvents(eventDispatcher: ViewModelEventDispatcher): void { - this._collectorCnt--; - if (this._collectorCnt === 0) { - const events = this._collector!.finalize(); - this._collector = null; - if (events.length > 0) { - eventDispatcher.emitMany(events); - } - } - } - - protected _emitSingleViewEvent(eventDispatcher: ViewModelEventDispatcher, event: ViewEvent): void { - try { - const eventsCollector = this._beginEmitViewEvents(); - eventsCollector.emit(event); - } finally { - this._endEmitViewEvents(eventDispatcher); - } - } -} - export class ViewEventsCollector { private _events: ViewEvent[]; @@ -388,5 +322,4 @@ export class ViewEventsCollector { this._events = []; return result; } - } diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index 961eeb314b1..3e72f30d0de 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -7,10 +7,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility, INewScrollPosition } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IConfiguration, IContentSizeChangedEvent, ScrollType } from 'vs/editor/common/editorCommon'; +import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; import { LinesLayout, IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel'; +import { ContentSizeChangedEvent } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; const SMOOTH_SCROLLING_TIME = 125; @@ -75,8 +76,8 @@ class EditorScrollable extends Disposable { public readonly onDidScroll: Event; - private readonly _onDidContentSizeChange = this._register(new Emitter()); - public readonly onDidContentSizeChange: Event = this._onDidContentSizeChange.event; + private readonly _onDidContentSizeChange = this._register(new Emitter()); + public readonly onDidContentSizeChange: Event = this._onDidContentSizeChange.event; constructor(smoothScrollDuration: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { super(); @@ -119,13 +120,10 @@ class EditorScrollable extends Disposable { const contentWidthChanged = (oldDimensions.contentWidth !== dimensions.contentWidth); const contentHeightChanged = (oldDimensions.contentHeight !== dimensions.contentHeight); if (contentWidthChanged || contentHeightChanged) { - this._onDidContentSizeChange.fire({ - contentWidth: dimensions.contentWidth, - contentHeight: dimensions.contentHeight, - - contentWidthChanged: contentWidthChanged, - contentHeightChanged: contentHeightChanged - }); + this._onDidContentSizeChange.fire(new ContentSizeChangedEvent( + oldDimensions.contentWidth, oldDimensions.contentHeight, + dimensions.contentWidth, dimensions.contentHeight + )); } } @@ -153,7 +151,7 @@ export class ViewLayout extends Disposable implements IViewLayout { private readonly _scrollable: EditorScrollable; public readonly onDidScroll: Event; - public readonly onDidContentSizeChange: Event; + public readonly onDidContentSizeChange: Event; constructor(configuration: IConfiguration, lineCount: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { super(); diff --git a/src/vs/editor/common/viewModel/viewEventHandler.ts b/src/vs/editor/common/viewModel/viewEventHandler.ts index b8d0bc823a6..aad7a0b4957 100644 --- a/src/vs/editor/common/viewModel/viewEventHandler.ts +++ b/src/vs/editor/common/viewModel/viewEventHandler.ts @@ -36,9 +36,7 @@ export class ViewEventHandler extends Disposable { public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { return false; } - public onContentSizeChanged(e: viewEvents.ViewContentSizeChangedEvent): boolean { - return false; - } + public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { return false; } @@ -102,12 +100,6 @@ export class ViewEventHandler extends Disposable { } break; - case viewEvents.ViewEventType.ViewContentSizeChanged: - if (this.onContentSizeChanged(e)) { - shouldRender = true; - } - break; - case viewEvents.ViewEventType.ViewCursorStateChanged: if (this.onCursorStateChanged(e)) { shouldRender = true; diff --git a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts index 515dfccc389..64bcbf65f67 100644 --- a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts +++ b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts @@ -4,21 +4,58 @@ *--------------------------------------------------------------------------------------------*/ import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; -import { ViewEvent } from 'vs/editor/common/view/viewEvents'; +import { ViewEvent, ViewEventsCollector } from 'vs/editor/common/view/viewEvents'; import { IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; -export class ViewModelEventDispatcher { +export class ViewModelEventDispatcher extends Disposable { + + private readonly _onEvent = this._register(new Emitter()); + public readonly onEvent = this._onEvent.event; private readonly _eventHandlers: ViewEventHandler[]; private _viewEventQueue: ViewEvent[] | null; private _isConsumingViewEventQueue: boolean; - // private _outgoingContentSizeChangedEvent: OutgoingContentSizeChangedEvent | null; + private _collector: ViewEventsCollector | null; + private _collectorCnt: number; + private _outgoingEvents: OutgoingViewModelEvent[]; constructor() { + super(); this._eventHandlers = []; this._viewEventQueue = null; this._isConsumingViewEventQueue = false; - // this._outgoingContentSizeChangedEvent = null; + this._collector = null; + this._collectorCnt = 0; + this._outgoingEvents = []; + } + + public emitOutgoingEvent(e: OutgoingViewModelEvent): void { + this._addOutgoingEvent(e); + this._emitOugoingEvents(); + } + + private _addOutgoingEvent(e: OutgoingViewModelEvent): void { + for (let i = 0, len = this._outgoingEvents.length; i < len; i++) { + if (this._outgoingEvents[i].kind === e.kind) { + this._outgoingEvents[i] = this._outgoingEvents[i].merge(e); + return; + } + } + // not merged + this._outgoingEvents.push(e); + } + + private _emitOugoingEvents(): void { + while (this._outgoingEvents.length > 0) { + if (this._collector || this._isConsumingViewEventQueue) { + // right now collecting or emitting view events, so let's postpone emitting + return; + } + const event = this._outgoingEvents.shift()!; + this._onEvent.fire(event); + } } public addViewEventHandler(eventHandler: ViewEventHandler): void { @@ -39,7 +76,36 @@ export class ViewModelEventDispatcher { } } - public emitMany(events: ViewEvent[]): void { + public beginEmitViewEvents(): ViewEventsCollector { + this._collectorCnt++; + if (this._collectorCnt === 1) { + this._collector = new ViewEventsCollector(); + } + return this._collector!; + } + + public endEmitViewEvents(): void { + this._collectorCnt--; + if (this._collectorCnt === 0) { + const events = this._collector!.finalize(); + this._collector = null; + if (events.length > 0) { + this._emitMany(events); + } + } + this._emitOugoingEvents(); + } + + public emitSingleViewEvent(event: ViewEvent): void { + try { + const eventsCollector = this.beginEmitViewEvents(); + eventsCollector.emit(event); + } finally { + this.endEmitViewEvents(); + } + } + + private _emitMany(events: ViewEvent[]): void { if (this._viewEventQueue) { this._viewEventQueue = this._viewEventQueue.concat(events); } else { @@ -75,7 +141,13 @@ export class ViewModelEventDispatcher { } } -export class OutgoingContentSizeChangedEvent implements IContentSizeChangedEvent { +export const enum OutgoingViewModelEventKind { + ContentSizeChanged, +} + +export class ContentSizeChangedEvent implements IContentSizeChangedEvent { + + public readonly kind = OutgoingViewModelEventKind.ContentSizeChanged; private readonly _oldContentWidth: number; private readonly _oldContentHeight: number; @@ -93,4 +165,10 @@ export class OutgoingContentSizeChangedEvent implements IContentSizeChangedEvent this.contentWidthChanged = (this._oldContentWidth !== this.contentWidth); this.contentHeightChanged = (this._oldContentHeight !== this.contentHeight); } + + public merge(other: ContentSizeChangedEvent): ContentSizeChangedEvent { + return new ContentSizeChangedEvent(this._oldContentWidth, this._oldContentHeight, other.contentWidth, other.contentHeight); + } } + +export type OutgoingViewModelEvent = ContentSizeChangedEvent; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 655f338c33b..fc5814ba3f0 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Color } from 'vs/base/common/color'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { ConfigurationChangedEvent, EDITOR_FONT_DEFAULTS, EditorOption, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -29,17 +30,18 @@ import { Cursor } from 'vs/editor/common/controller/cursor'; import { PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; -import { ViewModelEventDispatcher } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { ViewModelEventDispatcher, OutgoingViewModelEvent } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; const USE_IDENTITY_LINES_COLLECTION = true; -export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel { +export class ViewModel extends Disposable implements IViewModel { private readonly editorId: number; private readonly configuration: IConfiguration; public readonly model: ITextModel; private readonly _eventDispatcher: ViewModelEventDispatcher; + public readonly onEvent: Event; public cursorConfig: CursorConfiguration; private readonly _tokenizeViewportSoon: RunOnceScheduler; private readonly _updateConfigurationViewLineCount: RunOnceScheduler; @@ -67,6 +69,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.configuration = configuration; this.model = model; this._eventDispatcher = new ViewModelEventDispatcher(); + this.onEvent = this._eventDispatcher.onEvent; this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50)); this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); @@ -108,11 +111,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (e.scrollTopChanged) { this._tokenizeViewportSoon.schedule(); } - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewScrollChangedEvent(e)); + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewScrollChangedEvent(e)); })); this._register(this.viewLayout.onDidContentSizeChange((e) => { - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewContentSizeChangedEvent(e)); + this._eventDispatcher.emitOutgoingEvent(e); })); this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter); @@ -121,15 +124,15 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this._register(this.configuration.onDidChange((e) => { try { - const eventsCollector = this._beginEmitViewEvents(); + const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); this._onConfigurationChanged(eventsCollector, e); } finally { - this._endEmitViewEvents(this._eventDispatcher); + this._eventDispatcher.endEmitViewEvents(); } })); this._register(MinimapTokensColorTracker.getInstance().onDidChange(() => { - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewTokensColorsChangedEvent()); + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewTokensColorsChangedEvent()); })); this._updateConfigurationViewLineCountNow(); @@ -143,6 +146,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.lines.dispose(); this.invalidateMinimapColorCache(); this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); + this._eventDispatcher.dispose(); } public addViewEventHandler(eventHandler: ViewEventHandler): void { @@ -167,11 +171,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel public setHasFocus(hasFocus: boolean): void { this.hasFocus = hasFocus; this.cursor.setHasFocus(hasFocus); - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewFocusChangedEvent(hasFocus)); + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewFocusChangedEvent(hasFocus)); } public onDidColorThemeChange(): void { - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewThemeChangedEvent()); + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewThemeChangedEvent()); } private _onConfigurationChanged(eventsCollector: viewEvents.ViewEventsCollector, e: ConfigurationChangedEvent): void { @@ -231,7 +235,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this._register(this.model.onDidChangeRawContentFast((e) => { try { - const eventsCollector = this._beginEmitViewEvents(); + const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); let hadOtherModelChange = false; let hadModelLineChangeThatChangedLineMapping = false; @@ -325,7 +329,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.decorations.onLineMappingChanged(); } } finally { - this._endEmitViewEvents(this._eventDispatcher); + this._eventDispatcher.endEmitViewEvents(); } // Update the configuration and reset the centered view line @@ -344,10 +348,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel } try { - const eventsCollector = this._beginEmitViewEvents(); + const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); this.cursor.onModelContentChanged(eventsCollector, e); } finally { - this._endEmitViewEvents(this._eventDispatcher); + this._eventDispatcher.endEmitViewEvents(); } })); @@ -362,7 +366,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel toLineNumber: viewEndLineNumber }; } - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewTokensChangedEvent(viewRanges)); + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewTokensChangedEvent(viewRanges)); if (e.tokenizationSupportChanged) { this._tokenizeViewportSoon.schedule(); @@ -370,7 +374,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel })); this._register(this.model.onDidChangeLanguageConfiguration((e) => { - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewLanguageConfigurationEvent()); + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent()); this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); this.cursor.updateConfiguration(this.cursorConfig); })); @@ -384,7 +388,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel // A tab size change causes a line mapping changed event => all view parts will repaint OK, no further event needed here if (this.lines.setTabSize(this.model.getOptions().tabSize)) { try { - const eventsCollector = this._beginEmitViewEvents(); + const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); @@ -392,7 +396,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); } finally { - this._endEmitViewEvents(this._eventDispatcher); + this._eventDispatcher.endEmitViewEvents(); } this._updateConfigurationViewLineCount.schedule(); } @@ -403,13 +407,13 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this._register(this.model.onDidChangeDecorations((e) => { this.decorations.onModelDecorationsChanged(); - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewDecorationsChangedEvent(e)); + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e)); })); } public setHiddenAreas(ranges: Range[]): void { try { - const eventsCollector = this._beginEmitViewEvents(); + const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); let lineMappingChanged = this.lines.setHiddenAreas(ranges); if (lineMappingChanged) { eventsCollector.emit(new viewEvents.ViewFlushedEvent()); @@ -421,7 +425,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.viewLayout.onHeightMaybeChanged(); } } finally { - this._endEmitViewEvents(this._eventDispatcher); + this._eventDispatcher.endEmitViewEvents(); } this._updateConfigurationViewLineCount.schedule(); } @@ -970,7 +974,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): void { const hadAChange = this.viewLayout.changeWhitespace(callback); if (hadAChange) { - this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewZonesChangedEvent()); + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewZonesChangedEvent()); } } public setMaxLineWidth(maxLineWidth: number): void { @@ -980,10 +984,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel private _withViewEventsCollector(callback: (eventsCollector: viewEvents.ViewEventsCollector) => void): void { try { - const eventsCollector = this._beginEmitViewEvents(); + const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); callback(eventsCollector); } finally { - this._endEmitViewEvents(this._eventDispatcher); + this._eventDispatcher.endEmitViewEvents(); } } } From 5a1565cefefba0e334f65d9b40b364e5ab5976ab Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 09:55:49 +0200 Subject: [PATCH 10/21] Move scroll event emitting through view model (fixes #97447) --- src/vs/base/common/scrollable.ts | 16 +++ src/vs/editor/browser/view/viewImpl.ts | 9 -- .../editor/browser/view/viewOutgoingEvents.ts | 23 ---- .../editor/browser/widget/codeEditorWidget.ts | 13 ++- .../viewModel/viewModelEventDispatcher.ts | 100 +++++++++++++++++- .../editor/common/viewModel/viewModelImpl.ts | 7 +- 6 files changed, 126 insertions(+), 42 deletions(-) diff --git a/src/vs/base/common/scrollable.ts b/src/vs/base/common/scrollable.ts index c507366e853..a45e0763ab1 100644 --- a/src/vs/base/common/scrollable.ts +++ b/src/vs/base/common/scrollable.ts @@ -13,10 +13,18 @@ export const enum ScrollbarVisibility { } export interface ScrollEvent { + oldWidth: number; + oldScrollWidth: number; + oldScrollLeft: number; + width: number; scrollWidth: number; scrollLeft: number; + oldHeight: number; + oldScrollHeight: number; + oldScrollTop: number; + height: number; scrollHeight: number; scrollTop: number; @@ -134,10 +142,18 @@ export class ScrollState implements IScrollDimensions, IScrollPosition { const scrollTopChanged = (this.scrollTop !== previous.scrollTop); return { + oldWidth: previous.width, + oldScrollWidth: previous.scrollWidth, + oldScrollLeft: previous.scrollLeft, + width: this.width, scrollWidth: this.scrollWidth, scrollLeft: this.scrollLeft, + oldHeight: previous.height, + oldScrollHeight: previous.scrollHeight, + oldScrollTop: previous.scrollTop, + height: this.height, scrollHeight: this.scrollHeight, scrollTop: this.scrollTop, diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 19eb4fc14a5..d58c3f0fede 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -305,15 +305,6 @@ export class View extends ViewEventHandler { } public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { this.domNode.setClassName(this.getEditorClassName()); - if (e.isFocused) { - this.outgoingEvents.emitViewFocusGained(); - } else { - this.outgoingEvents.emitViewFocusLost(); - } - return false; - } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { - this.outgoingEvents.emitScrollChanged(e); return false; } public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { diff --git a/src/vs/editor/browser/view/viewOutgoingEvents.ts b/src/vs/editor/browser/view/viewOutgoingEvents.ts index 0c6e7c16615..2c7fa1db33b 100644 --- a/src/vs/editor/browser/view/viewOutgoingEvents.ts +++ b/src/vs/editor/browser/view/viewOutgoingEvents.ts @@ -9,8 +9,6 @@ import { MouseTarget } from 'vs/editor/browser/controller/mouseTarget'; import { IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IScrollEvent } from 'vs/editor/common/editorCommon'; -import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -20,9 +18,6 @@ export interface EventCallback { export class ViewOutgoingEvents extends Disposable { - public onDidScroll: EventCallback | null = null; - public onDidGainFocus: EventCallback | null = null; - public onDidLoseFocus: EventCallback | null = null; public onKeyDown: EventCallback | null = null; public onKeyUp: EventCallback | null = null; public onContextMenu: EventCallback | null = null; @@ -41,24 +36,6 @@ export class ViewOutgoingEvents extends Disposable { this._viewModel = viewModel; } - public emitScrollChanged(e: viewEvents.ViewScrollChangedEvent): void { - if (this.onDidScroll) { - this.onDidScroll(e); - } - } - - public emitViewFocusGained(): void { - if (this.onDidGainFocus) { - this.onDidGainFocus(undefined); - } - } - - public emitViewFocusLost(): void { - if (this.onDidLoseFocus) { - this.onDidLoseFocus(undefined); - } - } - public emitKeyDown(e: IKeyboardEvent): void { if (this.onKeyDown) { this.onKeyDown(e); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 592dde96f89..0f3aaa1bddf 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1515,6 +1515,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE case OutgoingViewModelEventKind.ContentSizeChanged: this._onDidContentSizeChange.fire(e); break; + case OutgoingViewModelEventKind.FocusChanged: + this._editorTextFocus.setValue(e.hasFocus); + break; + case OutgoingViewModelEventKind.ScrollChanged: + this._onDidScrollChange.fire(e); + break; } })); @@ -1596,14 +1602,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE }; } - const onDidChangeTextFocus = (textFocus: boolean) => { - this._editorTextFocus.setValue(textFocus); - }; - const viewOutgoingEvents = new ViewOutgoingEvents(viewModel); - viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e); - viewOutgoingEvents.onDidGainFocus = () => onDidChangeTextFocus(true); - viewOutgoingEvents.onDidLoseFocus = () => onDidChangeTextFocus(false); viewOutgoingEvents.onKeyDown = (e) => this._onKeyDown.fire(e); viewOutgoingEvents.onKeyUp = (e) => this._onKeyUp.fire(e); viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e); diff --git a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts index 64bcbf65f67..2bd933331e7 100644 --- a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts +++ b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts @@ -54,6 +54,9 @@ export class ViewModelEventDispatcher extends Disposable { return; } const event = this._outgoingEvents.shift()!; + if (event.isNoOp()) { + continue; + } this._onEvent.fire(event); } } @@ -143,6 +146,8 @@ export class ViewModelEventDispatcher extends Disposable { export const enum OutgoingViewModelEventKind { ContentSizeChanged, + FocusChanged, + ScrollChanged, } export class ContentSizeChangedEvent implements IContentSizeChangedEvent { @@ -166,9 +171,100 @@ export class ContentSizeChangedEvent implements IContentSizeChangedEvent { this.contentHeightChanged = (this._oldContentHeight !== this.contentHeight); } - public merge(other: ContentSizeChangedEvent): ContentSizeChangedEvent { + public isNoOp(): boolean { + return (!this.contentWidthChanged && !this.contentHeightChanged); + } + + + public merge(other: OutgoingViewModelEvent): ContentSizeChangedEvent { + if (other.kind !== OutgoingViewModelEventKind.ContentSizeChanged) { + return this; + } return new ContentSizeChangedEvent(this._oldContentWidth, this._oldContentHeight, other.contentWidth, other.contentHeight); } } -export type OutgoingViewModelEvent = ContentSizeChangedEvent; +export class FocusChangedEvent { + + public readonly kind = OutgoingViewModelEventKind.FocusChanged; + + readonly oldHasFocus: boolean; + readonly hasFocus: boolean; + + constructor(oldHasFocus: boolean, hasFocus: boolean) { + this.oldHasFocus = oldHasFocus; + this.hasFocus = hasFocus; + } + + public isNoOp(): boolean { + return (this.oldHasFocus === this.hasFocus); + } + + public merge(other: OutgoingViewModelEvent): FocusChangedEvent { + if (other.kind !== OutgoingViewModelEventKind.FocusChanged) { + return this; + } + return new FocusChangedEvent(this.oldHasFocus, other.hasFocus); + } +} + +export class ScrollChangedEvent { + + public readonly kind = OutgoingViewModelEventKind.ScrollChanged; + + private readonly _oldScrollWidth: number; + private readonly _oldScrollLeft: number; + private readonly _oldScrollHeight: number; + private readonly _oldScrollTop: number; + + public readonly scrollWidth: number; + public readonly scrollLeft: number; + public readonly scrollHeight: number; + public readonly scrollTop: number; + + public readonly scrollWidthChanged: boolean; + public readonly scrollLeftChanged: boolean; + public readonly scrollHeightChanged: boolean; + public readonly scrollTopChanged: boolean; + + constructor( + oldScrollWidth: number, oldScrollLeft: number, oldScrollHeight: number, oldScrollTop: number, + scrollWidth: number, scrollLeft: number, scrollHeight: number, scrollTop: number, + ) { + this._oldScrollWidth = oldScrollWidth; + this._oldScrollLeft = oldScrollLeft; + this._oldScrollHeight = oldScrollHeight; + this._oldScrollTop = oldScrollTop; + + this.scrollWidth = scrollWidth; + this.scrollLeft = scrollLeft; + this.scrollHeight = scrollHeight; + this.scrollTop = scrollTop; + + this.scrollWidthChanged = (this._oldScrollWidth !== this.scrollWidth); + this.scrollLeftChanged = (this._oldScrollLeft !== this.scrollLeft); + this.scrollHeightChanged = (this._oldScrollHeight !== this.scrollHeight); + this.scrollTopChanged = (this._oldScrollTop !== this.scrollTop); + } + + public isNoOp(): boolean { + return (!this.scrollWidthChanged && !this.scrollLeftChanged && !this.scrollHeightChanged && !this.scrollTopChanged); + } + + + public merge(other: OutgoingViewModelEvent): ScrollChangedEvent { + if (other.kind !== OutgoingViewModelEventKind.ScrollChanged) { + return this; + } + return new ScrollChangedEvent( + this._oldScrollWidth, this._oldScrollLeft, this._oldScrollHeight, this._oldScrollTop, + other.scrollWidth, other.scrollLeft, other.scrollHeight, other.scrollTop + ); + } +} + +export type OutgoingViewModelEvent = ( + ContentSizeChangedEvent + | FocusChangedEvent + | ScrollChangedEvent +); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index fc5814ba3f0..99de87ae6ab 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -30,7 +30,7 @@ import { Cursor } from 'vs/editor/common/controller/cursor'; import { PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; -import { ViewModelEventDispatcher, OutgoingViewModelEvent } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { ViewModelEventDispatcher, OutgoingViewModelEvent, FocusChangedEvent, ScrollChangedEvent } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -112,6 +112,10 @@ export class ViewModel extends Disposable implements IViewModel { this._tokenizeViewportSoon.schedule(); } this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewScrollChangedEvent(e)); + this._eventDispatcher.emitOutgoingEvent(new ScrollChangedEvent( + e.oldScrollWidth, e.oldScrollLeft, e.oldScrollHeight, e.oldScrollTop, + e.scrollWidth, e.scrollLeft, e.scrollHeight, e.scrollTop + )); })); this._register(this.viewLayout.onDidContentSizeChange((e) => { @@ -172,6 +176,7 @@ export class ViewModel extends Disposable implements IViewModel { this.hasFocus = hasFocus; this.cursor.setHasFocus(hasFocus); this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewFocusChangedEvent(hasFocus)); + this._eventDispatcher.emitOutgoingEvent(new FocusChangedEvent(!hasFocus, hasFocus)); } public onDidColorThemeChange(): void { From 4ba9ad5d6c43ca2c392c0125d1beabd14d80ce43 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 10:00:30 +0200 Subject: [PATCH 11/21] Emit view zones changed through view model --- src/vs/editor/browser/view/viewImpl.ts | 5 ++--- .../editor/browser/widget/codeEditorWidget.ts | 8 ++++---- .../viewModel/viewModelEventDispatcher.ts | 19 ++++++++++++++++++- .../editor/common/viewModel/viewModelImpl.ts | 3 ++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index d58c3f0fede..72397cdf9e8 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -450,10 +450,9 @@ export class View extends ViewEventHandler { return new OverviewRuler(this._context, cssClassName); } - public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean { - const hadAChange = this.viewZones.changeViewZones(callback); + public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): void { + this.viewZones.changeViewZones(callback); this._scheduleRender(); - return hadAChange; } public render(now: boolean, everything: boolean): void { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 0f3aaa1bddf..9cb79e71a6c 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1385,10 +1385,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData || !this._modelData.hasRealView) { return; } - const hasChanges = this._modelData.view.change(callback); - if (hasChanges) { - this._onDidChangeViewZones.fire(); - } + this._modelData.view.change(callback); } public getTargetAtClientPoint(clientX: number, clientY: number): editorBrowser.IMouseTarget | null { @@ -1521,6 +1518,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE case OutgoingViewModelEventKind.ScrollChanged: this._onDidScrollChange.fire(e); break; + case OutgoingViewModelEventKind.ViewZonesChanged: + this._onDidChangeViewZones.fire(); + break; } })); diff --git a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts index 2bd933331e7..8b8f5b4b907 100644 --- a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts +++ b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts @@ -148,6 +148,7 @@ export const enum OutgoingViewModelEventKind { ContentSizeChanged, FocusChanged, ScrollChanged, + ViewZonesChanged, } export class ContentSizeChangedEvent implements IContentSizeChangedEvent { @@ -251,7 +252,6 @@ export class ScrollChangedEvent { return (!this.scrollWidthChanged && !this.scrollLeftChanged && !this.scrollHeightChanged && !this.scrollTopChanged); } - public merge(other: OutgoingViewModelEvent): ScrollChangedEvent { if (other.kind !== OutgoingViewModelEventKind.ScrollChanged) { return this; @@ -263,8 +263,25 @@ export class ScrollChangedEvent { } } +export class ViewZonesChangedEvent { + + public readonly kind = OutgoingViewModelEventKind.ViewZonesChanged; + + constructor() { + } + + public isNoOp(): boolean { + return false; + } + + public merge(other: OutgoingViewModelEvent): ViewZonesChangedEvent { + return this; + } +} + export type OutgoingViewModelEvent = ( ContentSizeChangedEvent | FocusChangedEvent | ScrollChangedEvent + | ViewZonesChangedEvent ); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 99de87ae6ab..63663464456 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -30,7 +30,7 @@ import { Cursor } from 'vs/editor/common/controller/cursor'; import { PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; -import { ViewModelEventDispatcher, OutgoingViewModelEvent, FocusChangedEvent, ScrollChangedEvent } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { ViewModelEventDispatcher, OutgoingViewModelEvent, FocusChangedEvent, ScrollChangedEvent, ViewZonesChangedEvent } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -980,6 +980,7 @@ export class ViewModel extends Disposable implements IViewModel { const hadAChange = this.viewLayout.changeWhitespace(callback); if (hadAChange) { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewZonesChangedEvent()); + this._eventDispatcher.emitOutgoingEvent(new ViewZonesChangedEvent()); } } public setMaxLineWidth(maxLineWidth: number): void { From a8fe75f0637a96ab84393d7ec176945781134722 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 10:24:35 +0200 Subject: [PATCH 12/21] Emit cursor state change events through view model --- .../editor/browser/widget/codeEditorWidget.ts | 64 +++++------ src/vs/editor/common/controller/cursor.ts | 92 ++++------------ src/vs/editor/common/view/viewEvents.ts | 21 ---- .../viewModel/viewModelEventDispatcher.ts | 104 ++++++++++++++++-- .../editor/common/viewModel/viewModelImpl.ts | 50 ++++----- .../test/browser/controller/cursor.test.ts | 24 ++-- 6 files changed, 194 insertions(+), 161 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 9cb79e71a6c..16599432426 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -23,7 +23,7 @@ import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { IContentWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view/viewImpl'; import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents'; import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, IEditorConstructionOptions, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; -import { Cursor, CursorStateChangedEvent } from 'vs/editor/common/controller/cursor'; +import { Cursor } from 'vs/editor/common/controller/cursor'; import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -1477,36 +1477,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._onDidAttemptReadOnlyEdit.fire(undefined); })); - listenersToRemove.push(viewModel.cursor.onDidChange((e: CursorStateChangedEvent) => { - if (e.reachedMaxCursorCount) { - this._notificationService.warn(nls.localize('cursors.maximum', "The number of cursors has been limited to {0}.", Cursor.MAX_CURSOR_COUNT)); - } - - const positions: Position[] = []; - for (let i = 0, len = e.selections.length; i < len; i++) { - positions[i] = e.selections[i].getPosition(); - } - - const e1: ICursorPositionChangedEvent = { - position: positions[0], - secondaryPositions: positions.slice(1), - reason: e.reason, - source: e.source - }; - this._onDidChangeCursorPosition.fire(e1); - - const e2: ICursorSelectionChangedEvent = { - selection: e.selections[0], - secondarySelections: e.selections.slice(1), - modelVersionId: e.modelVersionId, - oldSelections: e.oldSelections, - oldModelVersionId: e.oldModelVersionId, - source: e.source, - reason: e.reason - }; - this._onDidChangeCursorSelection.fire(e2); - })); - listenersToRemove.push(viewModel.onEvent((e) => { switch (e.kind) { case OutgoingViewModelEventKind.ContentSizeChanged: @@ -1521,6 +1491,38 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE case OutgoingViewModelEventKind.ViewZonesChanged: this._onDidChangeViewZones.fire(); break; + case OutgoingViewModelEventKind.CursorStateChanged: { + if (e.reachedMaxCursorCount) { + this._notificationService.warn(nls.localize('cursors.maximum', "The number of cursors has been limited to {0}.", Cursor.MAX_CURSOR_COUNT)); + } + + const positions: Position[] = []; + for (let i = 0, len = e.selections.length; i < len; i++) { + positions[i] = e.selections[i].getPosition(); + } + + const e1: ICursorPositionChangedEvent = { + position: positions[0], + secondaryPositions: positions.slice(1), + reason: e.reason, + source: e.source + }; + this._onDidChangeCursorPosition.fire(e1); + + const e2: ICursorSelectionChangedEvent = { + selection: e.selections[0], + secondarySelections: e.selections.slice(1), + modelVersionId: e.modelVersionId, + oldSelections: e.oldSelections, + oldModelVersionId: e.oldModelVersionId, + source: e.source, + reason: e.reason + }; + this._onDidChangeCursorSelection.fire(e2); + + break; + } + } })); diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 55c36e06844..3934524b1a7 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -17,51 +17,10 @@ import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model'; import { RawContentChangedType, ModelRawContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { ViewEventsCollector, VerticalRevealType, ViewCursorStateChangedEvent, ViewRevealRangeRequestEvent } from 'vs/editor/common/view/viewEvents'; +import { VerticalRevealType, ViewCursorStateChangedEvent, ViewRevealRangeRequestEvent } from 'vs/editor/common/view/viewEvents'; import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; - -export class CursorStateChangedEvent { - /** - * The new selections. - * The primary selection is always at index 0. - */ - readonly selections: Selection[]; - /** - * The new model version id that `selections` apply to. - */ - readonly modelVersionId: number; - /** - * The old selections. - */ - readonly oldSelections: Selection[] | null; - /** - * The model version id the that `oldSelections` apply to. - */ - readonly oldModelVersionId: number; - /** - * Source of the call that caused the event. - */ - readonly source: string; - /** - * Reason. - */ - readonly reason: CursorChangeReason; - /** - * The number of cursors was limited because it has reached the maximum cursor count. - */ - readonly reachedMaxCursorCount: boolean; - - constructor(selections: Selection[], modelVersionId: number, oldSelections: Selection[] | null, oldModelVersionId: number, source: string, reason: CursorChangeReason, reachedMaxCursorCount: boolean) { - this.selections = selections; - this.modelVersionId = modelVersionId; - this.oldSelections = oldSelections; - this.oldModelVersionId = oldModelVersionId; - this.source = source; - this.reason = reason; - this.reachedMaxCursorCount = reachedMaxCursorCount; - } -} +import { CursorStateChangedEvent, ViewModelEventsCollector } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; /** * A snapshot of the cursor and the model state @@ -168,9 +127,6 @@ export class Cursor extends Disposable { private readonly _onDidAttemptReadOnlyEdit: Emitter = this._register(new Emitter()); public readonly onDidAttemptReadOnlyEdit: Event = this._onDidAttemptReadOnlyEdit.event; - private readonly _onDidChange: Emitter = this._register(new Emitter()); - public readonly onDidChange: Event = this._onDidChange.event; - private readonly _model: ITextModel; private _knownModelVersionId: number; private readonly _viewModel: ICursorSimpleModel; @@ -215,7 +171,7 @@ export class Cursor extends Disposable { this._cursors.updateContext(this.context); } - public onLineMappingChanged(eventsCollector: ViewEventsCollector): void { + public onLineMappingChanged(eventsCollector: ViewModelEventsCollector): void { if (this._knownModelVersionId !== this._model.getVersionId()) { // There are model change events that I didn't yet receive. // @@ -262,7 +218,7 @@ export class Cursor extends Disposable { return this._cursors.getAll(); } - public setStates(eventsCollector: ViewEventsCollector, source: string | null | undefined, reason: CursorChangeReason, states: PartialCursorState[] | null): boolean { + public setStates(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, reason: CursorChangeReason, states: PartialCursorState[] | null): boolean { let reachedMaxCursorCount = false; if (states !== null && states.length > Cursor.MAX_CURSOR_COUNT) { states = states.slice(0, Cursor.MAX_CURSOR_COUNT); @@ -284,7 +240,7 @@ export class Cursor extends Disposable { this._columnSelectData = columnSelectData; } - public revealPrimary(eventsCollector: ViewEventsCollector, source: string | null | undefined, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void { + public revealPrimary(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void { const viewPositions = this._cursors.getViewPositions(); if (viewPositions.length > 1) { this._emitCursorRevealRange(eventsCollector, source, null, this._cursors.getViewSelections(), VerticalRevealType.Simple, revealHorizontal, scrollType); @@ -296,7 +252,7 @@ export class Cursor extends Disposable { } } - private _revealPrimaryCursor(eventsCollector: ViewEventsCollector, source: string | null | undefined, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void { + private _revealPrimaryCursor(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void { const viewPositions = this._cursors.getViewPositions(); if (viewPositions.length > 1) { this._emitCursorRevealRange(eventsCollector, source, null, this._cursors.getViewSelections(), verticalType, revealHorizontal, scrollType); @@ -307,8 +263,8 @@ export class Cursor extends Disposable { } } - private _emitCursorRevealRange(eventsCollector: ViewEventsCollector, source: string | null | undefined, viewRange: Range | null, viewSelections: Selection[] | null, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType) { - eventsCollector.emit(new ViewRevealRangeRequestEvent(source, viewRange, viewSelections, verticalType, revealHorizontal, scrollType)); + private _emitCursorRevealRange(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, viewRange: Range | null, viewSelections: Selection[] | null, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType) { + eventsCollector.emitViewEvent(new ViewRevealRangeRequestEvent(source, viewRange, viewSelections, verticalType, revealHorizontal, scrollType)); } public saveState(): editorCommon.ICursorState[] { @@ -335,7 +291,7 @@ export class Cursor extends Disposable { return result; } - public restoreState(eventsCollector: ViewEventsCollector, states: editorCommon.ICursorState[]): void { + public restoreState(eventsCollector: ViewModelEventsCollector, states: editorCommon.ICursorState[]): void { let desiredSelections: ISelection[] = []; @@ -376,7 +332,7 @@ export class Cursor extends Disposable { this.revealPrimary(eventsCollector, 'restoreState', true, editorCommon.ScrollType.Immediate); } - public onModelContentChanged(eventsCollector: ViewEventsCollector, e: ModelRawContentChangedEvent): void { + public onModelContentChanged(eventsCollector: ViewModelEventsCollector, e: ModelRawContentChangedEvent): void { this._knownModelVersionId = e.versionId; if (this._isHandling) { @@ -441,7 +397,7 @@ export class Cursor extends Disposable { return this._cursors.getPrimaryCursor().modelState.position; } - public setSelections(eventsCollector: ViewEventsCollector, source: string | null | undefined, selections: readonly ISelection[]): void { + public setSelections(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, selections: readonly ISelection[]): void { this.setStates(eventsCollector, source, CursorChangeReason.NotSet, CursorState.fromModelSelections(selections)); } @@ -533,7 +489,7 @@ export class Cursor extends Disposable { // ----------------------------------------------------------------------------------------------------------- // ----- emitting events - private _emitStateChangedIfNecessary(eventsCollector: ViewEventsCollector, source: string | null | undefined, reason: CursorChangeReason, oldState: CursorModelState | null, reachedMaxCursorCount: boolean): boolean { + private _emitStateChangedIfNecessary(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, reason: CursorChangeReason, oldState: CursorModelState | null, reachedMaxCursorCount: boolean): boolean { const newState = new CursorModelState(this._model, this); if (newState.equals(oldState)) { return false; @@ -543,7 +499,7 @@ export class Cursor extends Disposable { const viewSelections = this._cursors.getViewSelections(); // Let the view get the event first. - eventsCollector.emit(new ViewCursorStateChangedEvent(viewSelections, selections)); + eventsCollector.emitViewEvent(new ViewCursorStateChangedEvent(viewSelections, selections)); // Only after the view has been notified, let the rest of the world know... if (!oldState @@ -552,7 +508,7 @@ export class Cursor extends Disposable { ) { const oldSelections = oldState ? oldState.cursorState.map(s => s.modelState.selection) : null; const oldModelVersionId = oldState ? oldState.modelVersionId : 0; - this._onDidChange.fire(new CursorStateChangedEvent(selections, newState.modelVersionId, oldSelections, oldModelVersionId, source || 'keyboard', reason, reachedMaxCursorCount)); + eventsCollector.emitOutgoingEvent(new CursorStateChangedEvent(oldSelections, selections, oldModelVersionId, newState.modelVersionId, source || 'keyboard', reason, reachedMaxCursorCount)); } return true; @@ -597,7 +553,7 @@ export class Cursor extends Disposable { return indices; } - public executeEdits(eventsCollector: ViewEventsCollector, source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): void { + public executeEdits(eventsCollector: ViewModelEventsCollector, source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): void { let autoClosingIndices: [number, number][] | null = null; if (source === 'snippet') { autoClosingIndices = this._findAutoClosingPairs(edits); @@ -639,7 +595,7 @@ export class Cursor extends Disposable { } } - private _executeEdit(callback: () => void, eventsCollector: ViewEventsCollector, source: string | null | undefined, cursorChangeReason: CursorChangeReason = CursorChangeReason.NotSet): void { + private _executeEdit(callback: () => void, eventsCollector: ViewModelEventsCollector, source: string | null | undefined, cursorChangeReason: CursorChangeReason = CursorChangeReason.NotSet): void { if (this.context.cursorConfig.readOnly) { // we cannot edit when read only... this._onDidAttemptReadOnlyEdit.fire(undefined); @@ -665,12 +621,12 @@ export class Cursor extends Disposable { } } - public startComposition(eventsCollector: ViewEventsCollector): void { + public startComposition(eventsCollector: ViewModelEventsCollector): void { this._isDoingComposition = true; this._selectionsWhenCompositionStarted = this.getSelections().slice(0); } - public endComposition(eventsCollector: ViewEventsCollector, source?: string | null | undefined): void { + public endComposition(eventsCollector: ViewModelEventsCollector, source?: string | null | undefined): void { this._isDoingComposition = false; this._executeEdit(() => { if (!this._isDoingComposition && source === 'keyboard') { @@ -682,7 +638,7 @@ export class Cursor extends Disposable { }, eventsCollector, source); } - public type(eventsCollector: ViewEventsCollector, text: string, source?: string | null | undefined): void { + public type(eventsCollector: ViewModelEventsCollector, text: string, source?: string | null | undefined): void { this._executeEdit(() => { if (source === 'keyboard') { // If this event is coming straight from the keyboard, look for electric characters and enter @@ -706,25 +662,25 @@ export class Cursor extends Disposable { }, eventsCollector, source); } - public replacePreviousChar(eventsCollector: ViewEventsCollector, text: string, replaceCharCnt: number, source?: string | null | undefined): void { + public replacePreviousChar(eventsCollector: ViewModelEventsCollector, text: string, replaceCharCnt: number, source?: string | null | undefined): void { this._executeEdit(() => { this._executeEditOperation(TypeOperations.replacePreviousChar(this._prevEditOperationType, this.context.cursorConfig, this._model, this.getSelections(), text, replaceCharCnt)); }, eventsCollector, source); } - public paste(eventsCollector: ViewEventsCollector, text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void { + public paste(eventsCollector: ViewModelEventsCollector, text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void { this._executeEdit(() => { this._executeEditOperation(TypeOperations.paste(this.context.cursorConfig, this._model, this.getSelections(), text, pasteOnNewLine, multicursorText || [])); }, eventsCollector, source, CursorChangeReason.Paste); } - public cut(eventsCollector: ViewEventsCollector, source?: string | null | undefined): void { + public cut(eventsCollector: ViewModelEventsCollector, source?: string | null | undefined): void { this._executeEdit(() => { this._executeEditOperation(DeleteOperations.cut(this.context.cursorConfig, this._model, this.getSelections())); }, eventsCollector, source); } - public executeCommand(eventsCollector: ViewEventsCollector, command: editorCommon.ICommand, source?: string | null | undefined): void { + public executeCommand(eventsCollector: ViewModelEventsCollector, command: editorCommon.ICommand, source?: string | null | undefined): void { this._executeEdit(() => { this._cursors.killSecondaryCursors(); @@ -735,7 +691,7 @@ export class Cursor extends Disposable { }, eventsCollector, source); } - public executeCommands(eventsCollector: ViewEventsCollector, commands: editorCommon.ICommand[], source?: string | null | undefined): void { + public executeCommands(eventsCollector: ViewModelEventsCollector, commands: editorCommon.ICommand[], source?: string | null | undefined): void { this._executeEdit(() => { this._executeEditOperation(new EditOperationResult(EditOperationType.Other, commands, { shouldPushStackElementBefore: false, diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 6c613e90418..804d1b977f5 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -302,24 +302,3 @@ export type ViewEvent = ( | ViewTokensColorsChangedEvent | ViewZonesChangedEvent ); - -export class ViewEventsCollector { - - private _events: ViewEvent[]; - private _eventsLen = 0; - - constructor() { - this._events = []; - this._eventsLen = 0; - } - - public emit(event: ViewEvent) { - this._events[this._eventsLen++] = event; - } - - public finalize(): ViewEvent[] { - let result = this._events; - this._events = []; - return result; - } -} diff --git a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts index 8b8f5b4b907..98994ea64f4 100644 --- a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts +++ b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; -import { ViewEvent, ViewEventsCollector } from 'vs/editor/common/view/viewEvents'; +import { ViewEvent } from 'vs/editor/common/view/viewEvents'; import { IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; import { Emitter } from 'vs/base/common/event'; +import { Selection } from 'vs/editor/common/core/selection'; import { Disposable } from 'vs/base/common/lifecycle'; +import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; export class ViewModelEventDispatcher extends Disposable { @@ -17,7 +19,7 @@ export class ViewModelEventDispatcher extends Disposable { private readonly _eventHandlers: ViewEventHandler[]; private _viewEventQueue: ViewEvent[] | null; private _isConsumingViewEventQueue: boolean; - private _collector: ViewEventsCollector | null; + private _collector: ViewModelEventsCollector | null; private _collectorCnt: number; private _outgoingEvents: OutgoingViewModelEvent[]; @@ -79,10 +81,10 @@ export class ViewModelEventDispatcher extends Disposable { } } - public beginEmitViewEvents(): ViewEventsCollector { + public beginEmitViewEvents(): ViewModelEventsCollector { this._collectorCnt++; if (this._collectorCnt === 1) { - this._collector = new ViewEventsCollector(); + this._collector = new ViewModelEventsCollector(); } return this._collector!; } @@ -90,10 +92,16 @@ export class ViewModelEventDispatcher extends Disposable { public endEmitViewEvents(): void { this._collectorCnt--; if (this._collectorCnt === 0) { - const events = this._collector!.finalize(); + const outgoingEvents = this._collector!.outgoingEvents; + const viewEvents = this._collector!.viewEvents; this._collector = null; - if (events.length > 0) { - this._emitMany(events); + + for (const outgoingEvent of outgoingEvents) { + this._addOutgoingEvent(outgoingEvent); + } + + if (viewEvents.length > 0) { + this._emitMany(viewEvents); } } this._emitOugoingEvents(); @@ -102,7 +110,7 @@ export class ViewModelEventDispatcher extends Disposable { public emitSingleViewEvent(event: ViewEvent): void { try { const eventsCollector = this.beginEmitViewEvents(); - eventsCollector.emit(event); + eventsCollector.emitViewEvent(event); } finally { this.endEmitViewEvents(); } @@ -144,11 +152,31 @@ export class ViewModelEventDispatcher extends Disposable { } } +export class ViewModelEventsCollector { + + public readonly viewEvents: ViewEvent[]; + public readonly outgoingEvents: OutgoingViewModelEvent[]; + + constructor() { + this.viewEvents = []; + this.outgoingEvents = []; + } + + public emitViewEvent(event: ViewEvent) { + this.viewEvents.push(event); + } + + public emitOutgoingEvent(e: OutgoingViewModelEvent): void { + this.outgoingEvents.push(e); + } +} + export const enum OutgoingViewModelEventKind { ContentSizeChanged, FocusChanged, ScrollChanged, ViewZonesChanged, + CursorStateChanged, } export class ContentSizeChangedEvent implements IContentSizeChangedEvent { @@ -279,9 +307,69 @@ export class ViewZonesChangedEvent { } } +export class CursorStateChangedEvent { + + public readonly kind = OutgoingViewModelEventKind.CursorStateChanged; + + public readonly oldSelections: Selection[] | null; + public readonly selections: Selection[]; + public readonly oldModelVersionId: number; + public readonly modelVersionId: number; + public readonly source: string; + public readonly reason: CursorChangeReason; + public readonly reachedMaxCursorCount: boolean; + + constructor(oldSelections: Selection[] | null, selections: Selection[], oldModelVersionId: number, modelVersionId: number, source: string, reason: CursorChangeReason, reachedMaxCursorCount: boolean) { + this.oldSelections = oldSelections; + this.selections = selections; + this.oldModelVersionId = oldModelVersionId; + this.modelVersionId = modelVersionId; + this.source = source; + this.reason = reason; + this.reachedMaxCursorCount = reachedMaxCursorCount; + } + + private static _selectionsAreEqual(a: Selection[] | null, b: Selection[] | null): boolean { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + const aLen = a.length; + const bLen = b.length; + if (aLen !== bLen) { + return false; + } + for (let i = 0; i < aLen; i++) { + if (!a[i].equalsSelection(b[i])) { + return false; + } + } + return true; + } + + public isNoOp(): boolean { + return ( + CursorStateChangedEvent._selectionsAreEqual(this.oldSelections, this.selections) + && this.oldModelVersionId === this.modelVersionId + ); + } + + public merge(other: OutgoingViewModelEvent): CursorStateChangedEvent { + if (other.kind !== OutgoingViewModelEventKind.CursorStateChanged) { + return this; + } + return new CursorStateChangedEvent( + this.oldSelections, other.selections, this.oldModelVersionId, other.modelVersionId, other.source, other.reason, this.reachedMaxCursorCount || other.reachedMaxCursorCount + ); + } +} + export type OutgoingViewModelEvent = ( ContentSizeChangedEvent | FocusChangedEvent | ScrollChangedEvent | ViewZonesChangedEvent + | CursorStateChangedEvent ); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 63663464456..14abe303e4c 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -30,7 +30,7 @@ import { Cursor } from 'vs/editor/common/controller/cursor'; import { PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; -import { ViewModelEventDispatcher, OutgoingViewModelEvent, FocusChangedEvent, ScrollChangedEvent, ViewZonesChangedEvent } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { ViewModelEventDispatcher, OutgoingViewModelEvent, FocusChangedEvent, ScrollChangedEvent, ViewZonesChangedEvent, ViewModelEventsCollector } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -183,7 +183,7 @@ export class ViewModel extends Disposable implements IViewModel { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewThemeChangedEvent()); } - private _onConfigurationChanged(eventsCollector: viewEvents.ViewEventsCollector, e: ConfigurationChangedEvent): void { + private _onConfigurationChanged(eventsCollector: ViewModelEventsCollector, e: ConfigurationChangedEvent): void { // We might need to restore the current centered view range, so save it (if available) let previousViewportStartModelPosition: Position | null = null; @@ -200,9 +200,9 @@ export class ViewModel extends Disposable implements IViewModel { const wrappingIndent = options.get(EditorOption.wrappingIndent); if (this.lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent)) { - eventsCollector.emit(new viewEvents.ViewFlushedEvent()); - eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); + eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); + eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); + eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); this.cursor.onLineMappingChanged(eventsCollector); this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); @@ -218,10 +218,10 @@ export class ViewModel extends Disposable implements IViewModel { if (e.hasChanged(EditorOption.readOnly)) { // Must read again all decorations due to readOnly filtering this.decorations.reset(); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); + eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); } - eventsCollector.emit(new viewEvents.ViewConfigurationChangedEvent(e)); + eventsCollector.emitViewEvent(new viewEvents.ViewConfigurationChangedEvent(e)); this.viewLayout.onConfigurationChanged(e); if (restorePreviousViewportStart && previousViewportStartModelPosition) { @@ -272,7 +272,7 @@ export class ViewModel extends Disposable implements IViewModel { switch (change.changeType) { case textModelEvents.RawContentChangedType.Flush: { this.lines.onModelFlushed(); - eventsCollector.emit(new viewEvents.ViewFlushedEvent()); + eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); this.decorations.reset(); this.viewLayout.onFlushed(this.getLineCount()); hadOtherModelChange = true; @@ -281,7 +281,7 @@ export class ViewModel extends Disposable implements IViewModel { case textModelEvents.RawContentChangedType.LinesDeleted: { const linesDeletedEvent = this.lines.onModelLinesDeleted(versionId, change.fromLineNumber, change.toLineNumber); if (linesDeletedEvent !== null) { - eventsCollector.emit(linesDeletedEvent); + eventsCollector.emitViewEvent(linesDeletedEvent); this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber); } hadOtherModelChange = true; @@ -293,7 +293,7 @@ export class ViewModel extends Disposable implements IViewModel { const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks); if (linesInsertedEvent !== null) { - eventsCollector.emit(linesInsertedEvent); + eventsCollector.emitViewEvent(linesInsertedEvent); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); } hadOtherModelChange = true; @@ -306,14 +306,14 @@ export class ViewModel extends Disposable implements IViewModel { const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData); hadModelLineChangeThatChangedLineMapping = lineMappingChanged; if (linesChangedEvent) { - eventsCollector.emit(linesChangedEvent); + eventsCollector.emitViewEvent(linesChangedEvent); } if (linesInsertedEvent) { - eventsCollector.emit(linesInsertedEvent); + eventsCollector.emitViewEvent(linesInsertedEvent); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); } if (linesDeletedEvent) { - eventsCollector.emit(linesDeletedEvent); + eventsCollector.emitViewEvent(linesDeletedEvent); this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber); } break; @@ -328,8 +328,8 @@ export class ViewModel extends Disposable implements IViewModel { this.viewLayout.onHeightMaybeChanged(); if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) { - eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); + eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); + eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); this.cursor.onLineMappingChanged(eventsCollector); this.decorations.onLineMappingChanged(); } @@ -394,9 +394,9 @@ export class ViewModel extends Disposable implements IViewModel { if (this.lines.setTabSize(this.model.getOptions().tabSize)) { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); - eventsCollector.emit(new viewEvents.ViewFlushedEvent()); - eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); + eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); + eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); + eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); this.cursor.onLineMappingChanged(eventsCollector); this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); @@ -421,9 +421,9 @@ export class ViewModel extends Disposable implements IViewModel { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); let lineMappingChanged = this.lines.setHiddenAreas(ranges); if (lineMappingChanged) { - eventsCollector.emit(new viewEvents.ViewFlushedEvent()); - eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); + eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); + eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); + eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); this.cursor.onLineMappingChanged(eventsCollector); this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); @@ -947,15 +947,15 @@ export class ViewModel extends Disposable implements IViewModel { public revealTopMostCursor(source: string | null | undefined): void { const viewPosition = this.cursor.getTopMostViewPosition(); const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); - this._withViewEventsCollector(eventsCollector => eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth))); + this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth))); } public revealBottomMostCursor(source: string | null | undefined): void { const viewPosition = this.cursor.getBottomMostViewPosition(); const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); - this._withViewEventsCollector(eventsCollector => eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth))); + this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth))); } public revealRange(source: string | null | undefined, revealHorizontal: boolean, viewRange: Range, verticalType: viewEvents.VerticalRevealType, scrollType: ScrollType): void { - this._withViewEventsCollector(eventsCollector => eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, null, verticalType, revealHorizontal, scrollType))); + this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, null, verticalType, revealHorizontal, scrollType))); } //#endregion @@ -988,7 +988,7 @@ export class ViewModel extends Disposable implements IViewModel { } //#endregion - private _withViewEventsCollector(callback: (eventsCollector: viewEvents.ViewEventsCollector) => void): void { + private _withViewEventsCollector(callback: (eventsCollector: ViewModelEventsCollector) => void): void { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); callback(eventsCollector); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 7eddc459cd7..d979c8cac5e 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import { CoreEditingCommands, CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { CursorStateChangedEvent } from 'vs/editor/common/controller/cursor'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -24,6 +23,7 @@ import { IRelaxedTextModelCreationOptions, createTextModel } from 'vs/editor/tes import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; +import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; // --------- utils @@ -782,7 +782,7 @@ suite('Editor Controller - Cursor', () => { test('no move doesn\'t trigger event', () => { runTest((editor, viewModel) => { - viewModel.cursor.onDidChange((e) => { + viewModel.onEvent((e) => { assert.ok(false, 'was not expecting event'); }); moveTo(editor, viewModel, 1, 1); @@ -792,9 +792,13 @@ suite('Editor Controller - Cursor', () => { test('move eventing', () => { runTest((editor, viewModel) => { let events = 0; - viewModel.cursor.onDidChange((e: CursorStateChangedEvent) => { - events++; - assert.deepEqual(e.selections, [new Selection(1, 2, 1, 2)]); + viewModel.onEvent((e) => { + if (e.kind === OutgoingViewModelEventKind.CursorStateChanged) { + events++; + assert.deepEqual(e.selections, [new Selection(1, 2, 1, 2)]); + } else { + assert.ok(false); + } }); moveTo(editor, viewModel, 1, 2); assert.equal(events, 1, 'receives 1 event'); @@ -804,9 +808,13 @@ suite('Editor Controller - Cursor', () => { test('move in selection mode eventing', () => { runTest((editor, viewModel) => { let events = 0; - viewModel.cursor.onDidChange((e: CursorStateChangedEvent) => { - events++; - assert.deepEqual(e.selections, [new Selection(1, 1, 1, 2)]); + viewModel.onEvent((e) => { + if (e.kind === OutgoingViewModelEventKind.CursorStateChanged) { + events++; + assert.deepEqual(e.selections, [new Selection(1, 1, 1, 2)]); + } else { + assert.ok(false); + } }); moveTo(editor, viewModel, 1, 2, true); assert.equal(events, 1, 'receives 1 event'); From 81e71a4a10648e6d79c648370e66495fa75c64b8 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 10:31:48 +0200 Subject: [PATCH 13/21] Rename ViewOutgoingEvents to ViewUserInputEvents --- src/vs/editor/browser/view/viewController.ts | 28 +++++++++---------- src/vs/editor/browser/view/viewImpl.ts | 12 +++----- ...tgoingEvents.ts => viewUserInputEvents.ts} | 14 ++++------ .../editor/browser/widget/codeEditorWidget.ts | 26 ++++++++--------- 4 files changed, 37 insertions(+), 43 deletions(-) rename src/vs/editor/browser/view/{viewOutgoingEvents.ts => viewUserInputEvents.ts} (91%) diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index 96958cf8fff..6fc163d9c36 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -6,7 +6,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { CoreEditorCommand, CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands'; import { IEditorMouseEvent, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; -import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents'; +import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { IConfiguration } from 'vs/editor/common/editorCommon'; @@ -49,18 +49,18 @@ export class ViewController { private readonly configuration: IConfiguration; private readonly viewModel: IViewModel; - private readonly outgoingEvents: ViewOutgoingEvents; + private readonly userInputEvents: ViewUserInputEvents; private readonly commandDelegate: ICommandDelegate; constructor( configuration: IConfiguration, viewModel: IViewModel, - outgoingEvents: ViewOutgoingEvents, + userInputEvents: ViewUserInputEvents, commandDelegate: ICommandDelegate ) { this.configuration = configuration; this.viewModel = viewModel; - this.outgoingEvents = outgoingEvents; + this.userInputEvents = userInputEvents; this.commandDelegate = commandDelegate; } @@ -289,42 +289,42 @@ export class ViewController { } public emitKeyDown(e: IKeyboardEvent): void { - this.outgoingEvents.emitKeyDown(e); + this.userInputEvents.emitKeyDown(e); } public emitKeyUp(e: IKeyboardEvent): void { - this.outgoingEvents.emitKeyUp(e); + this.userInputEvents.emitKeyUp(e); } public emitContextMenu(e: IEditorMouseEvent): void { - this.outgoingEvents.emitContextMenu(e); + this.userInputEvents.emitContextMenu(e); } public emitMouseMove(e: IEditorMouseEvent): void { - this.outgoingEvents.emitMouseMove(e); + this.userInputEvents.emitMouseMove(e); } public emitMouseLeave(e: IPartialEditorMouseEvent): void { - this.outgoingEvents.emitMouseLeave(e); + this.userInputEvents.emitMouseLeave(e); } public emitMouseUp(e: IEditorMouseEvent): void { - this.outgoingEvents.emitMouseUp(e); + this.userInputEvents.emitMouseUp(e); } public emitMouseDown(e: IEditorMouseEvent): void { - this.outgoingEvents.emitMouseDown(e); + this.userInputEvents.emitMouseDown(e); } public emitMouseDrag(e: IEditorMouseEvent): void { - this.outgoingEvents.emitMouseDrag(e); + this.userInputEvents.emitMouseDrag(e); } public emitMouseDrop(e: IPartialEditorMouseEvent): void { - this.outgoingEvents.emitMouseDrop(e); + this.userInputEvents.emitMouseDrop(e); } public emitMouseWheel(e: IMouseWheelEvent): void { - this.outgoingEvents.emitMouseWheel(e); + this.userInputEvents.emitMouseWheel(e); } } diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 72397cdf9e8..f57574a6906 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -14,7 +14,7 @@ import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; import { ITextAreaHandlerHelper, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler'; import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor, IEditorAriaOptions } from 'vs/editor/browser/editorBrowser'; import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController'; -import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents'; +import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; import { ViewContentWidgets } from 'vs/editor/browser/viewParts/contentWidgets/contentWidgets'; @@ -80,8 +80,6 @@ export class View extends ViewEventHandler { private readonly _textAreaHandler: TextAreaHandler; private readonly pointerHandler: PointerHandler; - private readonly outgoingEvents: ViewOutgoingEvents; - // Dom nodes private linesContent: FastDomNode; public domNode: FastDomNode; @@ -95,14 +93,13 @@ export class View extends ViewEventHandler { configuration: IConfiguration, themeService: IThemeService, model: IViewModel, - outgoingEvents: ViewOutgoingEvents + userInputEvents: ViewUserInputEvents ) { super(); this._selections = [new Selection(1, 1, 1, 1)]; this._renderAnimationFrame = null; - this.outgoingEvents = outgoingEvents; - const viewController = new ViewController(configuration, model, this.outgoingEvents, commandDelegate); + const viewController = new ViewController(configuration, model, userInputEvents, commandDelegate); // The view context is passed on to most classes (basically to reduce param. counts in ctors) this._context = new ViewContext(configuration, themeService.getColorTheme(), model); @@ -321,7 +318,6 @@ export class View extends ViewEventHandler { } this._context.removeEventHandler(this); - this.outgoingEvents.dispose(); this.viewLines.dispose(); @@ -443,7 +439,7 @@ export class View extends ViewEventHandler { if (!mouseTarget) { return null; } - return ViewOutgoingEvents.convertViewToModelMouseTarget(mouseTarget, this._context.model.coordinatesConverter); + return ViewUserInputEvents.convertViewToModelMouseTarget(mouseTarget, this._context.model.coordinatesConverter); } public createOverviewRuler(cssClassName: string): OverviewRuler { diff --git a/src/vs/editor/browser/view/viewOutgoingEvents.ts b/src/vs/editor/browser/view/viewUserInputEvents.ts similarity index 91% rename from src/vs/editor/browser/view/viewOutgoingEvents.ts rename to src/vs/editor/browser/view/viewUserInputEvents.ts index 2c7fa1db33b..22906bda60e 100644 --- a/src/vs/editor/browser/view/viewOutgoingEvents.ts +++ b/src/vs/editor/browser/view/viewUserInputEvents.ts @@ -4,19 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { Disposable } from 'vs/base/common/lifecycle'; import { MouseTarget } from 'vs/editor/browser/controller/mouseTarget'; import { IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; +import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; export interface EventCallback { (event: T): void; } -export class ViewOutgoingEvents extends Disposable { +export class ViewUserInputEvents { public onKeyDown: EventCallback | null = null; public onKeyUp: EventCallback | null = null; @@ -29,11 +28,10 @@ export class ViewOutgoingEvents extends Disposable { public onMouseDrop: EventCallback | null = null; public onMouseWheel: EventCallback | null = null; - private readonly _viewModel: IViewModel; + private readonly _coordinatesConverter: ICoordinatesConverter; - constructor(viewModel: IViewModel) { - super(); - this._viewModel = viewModel; + constructor(coordinatesConverter: ICoordinatesConverter) { + this._coordinatesConverter = coordinatesConverter; } public emitKeyDown(e: IKeyboardEvent): void { @@ -109,7 +107,7 @@ export class ViewOutgoingEvents extends Disposable { } private _convertViewToModelMouseTarget(target: IMouseTarget): IMouseTarget { - return ViewOutgoingEvents.convertViewToModelMouseTarget(target, this._viewModel.coordinatesConverter); + return ViewUserInputEvents.convertViewToModelMouseTarget(target, this._coordinatesConverter); } public static convertViewToModelMouseTarget(target: IMouseTarget, coordinatesConverter: ICoordinatesConverter): IMouseTarget { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 16599432426..81ccfb8dbf8 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -21,7 +21,7 @@ import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/edi import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { IContentWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view/viewImpl'; -import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents'; +import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, IEditorConstructionOptions, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { Cursor } from 'vs/editor/common/controller/cursor'; import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; @@ -1604,24 +1604,24 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE }; } - const viewOutgoingEvents = new ViewOutgoingEvents(viewModel); - viewOutgoingEvents.onKeyDown = (e) => this._onKeyDown.fire(e); - viewOutgoingEvents.onKeyUp = (e) => this._onKeyUp.fire(e); - viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e); - viewOutgoingEvents.onMouseMove = (e) => this._onMouseMove.fire(e); - viewOutgoingEvents.onMouseLeave = (e) => this._onMouseLeave.fire(e); - viewOutgoingEvents.onMouseDown = (e) => this._onMouseDown.fire(e); - viewOutgoingEvents.onMouseUp = (e) => this._onMouseUp.fire(e); - viewOutgoingEvents.onMouseDrag = (e) => this._onMouseDrag.fire(e); - viewOutgoingEvents.onMouseDrop = (e) => this._onMouseDrop.fire(e); - viewOutgoingEvents.onMouseWheel = (e) => this._onMouseWheel.fire(e); + const viewUserInputEvents = new ViewUserInputEvents(viewModel.coordinatesConverter); + viewUserInputEvents.onKeyDown = (e) => this._onKeyDown.fire(e); + viewUserInputEvents.onKeyUp = (e) => this._onKeyUp.fire(e); + viewUserInputEvents.onContextMenu = (e) => this._onContextMenu.fire(e); + viewUserInputEvents.onMouseMove = (e) => this._onMouseMove.fire(e); + viewUserInputEvents.onMouseLeave = (e) => this._onMouseLeave.fire(e); + viewUserInputEvents.onMouseDown = (e) => this._onMouseDown.fire(e); + viewUserInputEvents.onMouseUp = (e) => this._onMouseUp.fire(e); + viewUserInputEvents.onMouseDrag = (e) => this._onMouseDrag.fire(e); + viewUserInputEvents.onMouseDrop = (e) => this._onMouseDrop.fire(e); + viewUserInputEvents.onMouseWheel = (e) => this._onMouseWheel.fire(e); const view = new View( commandDelegate, this._configuration, this._themeService, viewModel, - viewOutgoingEvents + viewUserInputEvents ); return [view, true]; From b4631d39c19e5fdc1449ae0bcfaea6949ef6c097 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 11:57:43 +0200 Subject: [PATCH 14/21] Emit ReadOnlyEditAttemptEvent through view model --- .../editor/browser/widget/codeEditorWidget.ts | 7 ++-- src/vs/editor/common/controller/cursor.ts | 13 +++----- .../viewModel/viewModelEventDispatcher.ts | 18 +++++++++++ .../editor/common/viewModel/viewModelImpl.ts | 32 ++++++++++++------- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 81ccfb8dbf8..f57ab7693d6 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1473,10 +1473,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE // Someone might destroy the model from under the editor, so prevent any exceptions by setting a null model listenersToRemove.push(model.onWillDispose(() => this.setModel(null))); - listenersToRemove.push(viewModel.cursor.onDidAttemptReadOnlyEdit(() => { - this._onDidAttemptReadOnlyEdit.fire(undefined); - })); - listenersToRemove.push(viewModel.onEvent((e) => { switch (e.kind) { case OutgoingViewModelEventKind.ContentSizeChanged: @@ -1491,6 +1487,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE case OutgoingViewModelEventKind.ViewZonesChanged: this._onDidChangeViewZones.fire(); break; + case OutgoingViewModelEventKind.ReadOnlyEditAttempt: + this._onDidAttemptReadOnlyEdit.fire(); + break; case OutgoingViewModelEventKind.CursorStateChanged: { if (e.reachedMaxCursorCount) { this._notificationService.warn(nls.localize('cursors.maximum', "The number of cursors has been limited to {0}.", Cursor.MAX_CURSOR_COUNT)); diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 3934524b1a7..7a966ed223e 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; import * as strings from 'vs/base/common/strings'; import { CursorCollection } from 'vs/editor/common/controller/cursorCollection'; import { CursorColumns, CursorConfiguration, CursorContext, CursorState, EditOperationResult, EditOperationType, IColumnSelectData, PartialCursorState, ICursorSimpleModel } from 'vs/editor/common/controller/cursorCommon'; @@ -124,9 +123,6 @@ export class Cursor extends Disposable { public static readonly MAX_CURSOR_COUNT = 10000; - private readonly _onDidAttemptReadOnlyEdit: Emitter = this._register(new Emitter()); - public readonly onDidAttemptReadOnlyEdit: Event = this._onDidAttemptReadOnlyEdit.event; - private readonly _model: ITextModel; private _knownModelVersionId: number; private readonly _viewModel: ICursorSimpleModel; @@ -598,7 +594,6 @@ export class Cursor extends Disposable { private _executeEdit(callback: () => void, eventsCollector: ViewModelEventsCollector, source: string | null | undefined, cursorChangeReason: CursorChangeReason = CursorChangeReason.NotSet): void { if (this.context.cursorConfig.readOnly) { // we cannot edit when read only... - this._onDidAttemptReadOnlyEdit.fire(undefined); return; } @@ -621,15 +616,17 @@ export class Cursor extends Disposable { } } + public setIsDoingComposition(isDoingComposition: boolean): void { + this._isDoingComposition = isDoingComposition; + } + public startComposition(eventsCollector: ViewModelEventsCollector): void { - this._isDoingComposition = true; this._selectionsWhenCompositionStarted = this.getSelections().slice(0); } public endComposition(eventsCollector: ViewModelEventsCollector, source?: string | null | undefined): void { - this._isDoingComposition = false; this._executeEdit(() => { - if (!this._isDoingComposition && source === 'keyboard') { + if (source === 'keyboard') { // composition finishes, let's check if we need to auto complete if necessary. const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions); this._executeEditOperation(TypeOperations.compositionEndWithInterceptors(this._prevEditOperationType, this.context.cursorConfig, this._model, this._selectionsWhenCompositionStarted, this.getSelections(), autoClosedCharacters)); diff --git a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts index 98994ea64f4..0691b542cd1 100644 --- a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts +++ b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts @@ -176,6 +176,7 @@ export const enum OutgoingViewModelEventKind { FocusChanged, ScrollChanged, ViewZonesChanged, + ReadOnlyEditAttempt, CursorStateChanged, } @@ -366,10 +367,27 @@ export class CursorStateChangedEvent { } } +export class ReadOnlyEditAttemptEvent { + + public readonly kind = OutgoingViewModelEventKind.ReadOnlyEditAttempt; + + constructor() { + } + + public isNoOp(): boolean { + return false; + } + + public merge(other: OutgoingViewModelEvent): ReadOnlyEditAttemptEvent { + return this; + } +} + export type OutgoingViewModelEvent = ( ContentSizeChangedEvent | FocusChangedEvent | ScrollChangedEvent | ViewZonesChangedEvent + | ReadOnlyEditAttemptEvent | CursorStateChangedEvent ); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 14abe303e4c..c59d0fe4b40 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -30,7 +30,7 @@ import { Cursor } from 'vs/editor/common/controller/cursor'; import { PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; -import { ViewModelEventDispatcher, OutgoingViewModelEvent, FocusChangedEvent, ScrollChangedEvent, ViewZonesChangedEvent, ViewModelEventsCollector } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { ViewModelEventDispatcher, OutgoingViewModelEvent, FocusChangedEvent, ScrollChangedEvent, ViewZonesChangedEvent, ViewModelEventsCollector, ReadOnlyEditAttemptEvent } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -52,7 +52,7 @@ export class ViewModel extends Disposable implements IViewModel { private readonly lines: IViewModelLinesCollection; public readonly coordinatesConverter: ICoordinatesConverter; public readonly viewLayout: ViewLayout; - public readonly cursor: Cursor; + private readonly cursor: Cursor; private readonly decorations: ViewModelDecorations; constructor( @@ -914,32 +914,42 @@ export class ViewModel extends Disposable implements IViewModel { this._withViewEventsCollector(eventsCollector => this.cursor.restoreState(eventsCollector, states)); } + private _executeCursorEdit(callback: (eventsCollector: ViewModelEventsCollector) => void): void { + if (this.cursor.context.cursorConfig.readOnly) { + // we cannot edit when read only... + this._eventDispatcher.emitOutgoingEvent(new ReadOnlyEditAttemptEvent()); + return; + } + this._withViewEventsCollector(callback); + } public executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): void { - this._withViewEventsCollector(eventsCollector => this.cursor.executeEdits(eventsCollector, source, edits, cursorStateComputer)); + this._executeCursorEdit(eventsCollector => this.cursor.executeEdits(eventsCollector, source, edits, cursorStateComputer)); } public startComposition(): void { - this._withViewEventsCollector(eventsCollector => this.cursor.startComposition(eventsCollector)); + this.cursor.setIsDoingComposition(true); + this._executeCursorEdit(eventsCollector => this.cursor.startComposition(eventsCollector)); } public endComposition(source?: string | null | undefined): void { - this._withViewEventsCollector(eventsCollector => this.cursor.endComposition(eventsCollector, source)); + this.cursor.setIsDoingComposition(false); + this._executeCursorEdit(eventsCollector => this.cursor.endComposition(eventsCollector, source)); } public type(text: string, source?: string | null | undefined): void { - this._withViewEventsCollector(eventsCollector => this.cursor.type(eventsCollector, text, source)); + this._executeCursorEdit(eventsCollector => this.cursor.type(eventsCollector, text, source)); } public replacePreviousChar(text: string, replaceCharCnt: number, source?: string | null | undefined): void { - this._withViewEventsCollector(eventsCollector => this.cursor.replacePreviousChar(eventsCollector, text, replaceCharCnt, source)); + this._executeCursorEdit(eventsCollector => this.cursor.replacePreviousChar(eventsCollector, text, replaceCharCnt, source)); } public paste(text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void { - this._withViewEventsCollector(eventsCollector => this.cursor.paste(eventsCollector, text, pasteOnNewLine, multicursorText, source)); + this._executeCursorEdit(eventsCollector => this.cursor.paste(eventsCollector, text, pasteOnNewLine, multicursorText, source)); } public cut(source?: string | null | undefined): void { - this._withViewEventsCollector(eventsCollector => this.cursor.cut(eventsCollector, source)); + this._executeCursorEdit(eventsCollector => this.cursor.cut(eventsCollector, source)); } public executeCommand(command: ICommand, source?: string | null | undefined): void { - this._withViewEventsCollector(eventsCollector => this.cursor.executeCommand(eventsCollector, command, source)); + this._executeCursorEdit(eventsCollector => this.cursor.executeCommand(eventsCollector, command, source)); } public executeCommands(commands: ICommand[], source?: string | null | undefined): void { - this._withViewEventsCollector(eventsCollector => this.cursor.executeCommands(eventsCollector, commands, source)); + this._executeCursorEdit(eventsCollector => this.cursor.executeCommands(eventsCollector, commands, source)); } public revealPrimaryCursor(source: string | null | undefined, revealHorizontal: boolean): void { this._withViewEventsCollector(eventsCollector => this.cursor.revealPrimary(eventsCollector, source, revealHorizontal, ScrollType.Smooth)); From 9cd365eef2551cf011df682cee43d5d8c91e4d09 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 12:00:46 +0200 Subject: [PATCH 15/21] :lipstick: Minor renames --- .../editor/common/viewModel/viewModelImpl.ts | 234 +++++++++--------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index c59d0fe4b40..fae83ed8666 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -37,23 +37,23 @@ const USE_IDENTITY_LINES_COLLECTION = true; export class ViewModel extends Disposable implements IViewModel { - private readonly editorId: number; - private readonly configuration: IConfiguration; + private readonly _editorId: number; + private readonly _configuration: IConfiguration; public readonly model: ITextModel; private readonly _eventDispatcher: ViewModelEventDispatcher; public readonly onEvent: Event; public cursorConfig: CursorConfiguration; private readonly _tokenizeViewportSoon: RunOnceScheduler; private readonly _updateConfigurationViewLineCount: RunOnceScheduler; - private hasFocus: boolean; - private viewportStartLine: number; - private viewportStartLineTrackedRange: string | null; - private viewportStartLineDelta: number; - private readonly lines: IViewModelLinesCollection; + private _hasFocus: boolean; + private _viewportStartLine: number; + private _viewportStartLineTrackedRange: string | null; + private _viewportStartLineDelta: number; + private readonly _lines: IViewModelLinesCollection; public readonly coordinatesConverter: ICoordinatesConverter; public readonly viewLayout: ViewLayout; - private readonly cursor: Cursor; - private readonly decorations: ViewModelDecorations; + private readonly _cursor: Cursor; + private readonly _decorations: ViewModelDecorations; constructor( editorId: number, @@ -65,31 +65,31 @@ export class ViewModel extends Disposable implements IViewModel { ) { super(); - this.editorId = editorId; - this.configuration = configuration; + this._editorId = editorId; + this._configuration = configuration; this.model = model; this._eventDispatcher = new ViewModelEventDispatcher(); this.onEvent = this._eventDispatcher.onEvent; - this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this._configuration); this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50)); this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); - this.hasFocus = false; - this.viewportStartLine = -1; - this.viewportStartLineTrackedRange = null; - this.viewportStartLineDelta = 0; + this._hasFocus = false; + this._viewportStartLine = -1; + this._viewportStartLineTrackedRange = null; + this._viewportStartLineDelta = 0; if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) { - this.lines = new IdentityLinesCollection(this.model); + this._lines = new IdentityLinesCollection(this.model); } else { - const options = this.configuration.options; + const options = this._configuration.options; const fontInfo = options.get(EditorOption.fontInfo); const wrappingStrategy = options.get(EditorOption.wrappingStrategy); const wrappingInfo = options.get(EditorOption.wrappingInfo); const wrappingIndent = options.get(EditorOption.wrappingIndent); - this.lines = new SplitLinesCollection( + this._lines = new SplitLinesCollection( this.model, domLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, @@ -101,11 +101,11 @@ export class ViewModel extends Disposable implements IViewModel { ); } - this.coordinatesConverter = this.lines.createCoordinatesConverter(); + this.coordinatesConverter = this._lines.createCoordinatesConverter(); - this.cursor = this._register(new Cursor(model, this, this.coordinatesConverter, this.cursorConfig)); + this._cursor = this._register(new Cursor(model, this, this.coordinatesConverter, this.cursorConfig)); - this.viewLayout = this._register(new ViewLayout(this.configuration, this.getLineCount(), scheduleAtNextAnimationFrame)); + this.viewLayout = this._register(new ViewLayout(this._configuration, this.getLineCount(), scheduleAtNextAnimationFrame)); this._register(this.viewLayout.onDidScroll((e) => { if (e.scrollTopChanged) { @@ -122,11 +122,11 @@ export class ViewModel extends Disposable implements IViewModel { this._eventDispatcher.emitOutgoingEvent(e); })); - this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter); + this._decorations = new ViewModelDecorations(this._editorId, this.model, this._configuration, this._lines, this.coordinatesConverter); this._registerModelEvents(); - this._register(this.configuration.onDidChange((e) => { + this._register(this._configuration.onDidChange((e) => { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); this._onConfigurationChanged(eventsCollector, e); @@ -146,10 +146,10 @@ export class ViewModel extends Disposable implements IViewModel { // First remove listeners, as disposing the lines might end up sending // model decoration changed events ... and we no longer care about them ... super.dispose(); - this.decorations.dispose(); - this.lines.dispose(); + this._decorations.dispose(); + this._lines.dispose(); this.invalidateMinimapColorCache(); - this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); + this._viewportStartLineTrackedRange = this.model._setTrackedRange(this._viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); this._eventDispatcher.dispose(); } @@ -162,7 +162,7 @@ export class ViewModel extends Disposable implements IViewModel { } private _updateConfigurationViewLineCountNow(): void { - this.configuration.setViewLineCount(this.lines.getViewLineCount()); + this._configuration.setViewLineCount(this._lines.getViewLineCount()); } public tokenizeViewport(): void { @@ -173,8 +173,8 @@ export class ViewModel extends Disposable implements IViewModel { } public setHasFocus(hasFocus: boolean): void { - this.hasFocus = hasFocus; - this.cursor.setHasFocus(hasFocus); + this._hasFocus = hasFocus; + this._cursor.setHasFocus(hasFocus); this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewFocusChangedEvent(hasFocus)); this._eventDispatcher.emitOutgoingEvent(new FocusChangedEvent(!hasFocus, hasFocus)); } @@ -187,24 +187,24 @@ export class ViewModel extends Disposable implements IViewModel { // We might need to restore the current centered view range, so save it (if available) let previousViewportStartModelPosition: Position | null = null; - if (this.viewportStartLine !== -1) { - let previousViewportStartViewPosition = new Position(this.viewportStartLine, this.getLineMinColumn(this.viewportStartLine)); + if (this._viewportStartLine !== -1) { + let previousViewportStartViewPosition = new Position(this._viewportStartLine, this.getLineMinColumn(this._viewportStartLine)); previousViewportStartModelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(previousViewportStartViewPosition); } let restorePreviousViewportStart = false; - const options = this.configuration.options; + const options = this._configuration.options; const fontInfo = options.get(EditorOption.fontInfo); const wrappingStrategy = options.get(EditorOption.wrappingStrategy); const wrappingInfo = options.get(EditorOption.wrappingInfo); const wrappingIndent = options.get(EditorOption.wrappingIndent); - if (this.lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent)) { + if (this._lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent)) { eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); - this.cursor.onLineMappingChanged(eventsCollector); - this.decorations.onLineMappingChanged(); + this._cursor.onLineMappingChanged(eventsCollector); + this._decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); if (this.viewLayout.getCurrentScrollTop() !== 0) { @@ -217,7 +217,7 @@ export class ViewModel extends Disposable implements IViewModel { if (e.hasChanged(EditorOption.readOnly)) { // Must read again all decorations due to readOnly filtering - this.decorations.reset(); + this._decorations.reset(); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); } @@ -227,12 +227,12 @@ export class ViewModel extends Disposable implements IViewModel { if (restorePreviousViewportStart && previousViewportStartModelPosition) { const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(previousViewportStartModelPosition); const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); - this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this.viewportStartLineDelta }, ScrollType.Immediate); + this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this._viewportStartLineDelta }, ScrollType.Immediate); } if (CursorConfiguration.shouldRecreate(e)) { - this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); - this.cursor.updateConfiguration(this.cursorConfig); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this._configuration); + this._cursor.updateConfiguration(this.cursorConfig); } } @@ -249,7 +249,7 @@ export class ViewModel extends Disposable implements IViewModel { const versionId = e.versionId; // Do a first pass to compute line mappings, and a second pass to actually interpret them - const lineBreaksComputer = this.lines.createLineBreaksComputer(); + const lineBreaksComputer = this._lines.createLineBreaksComputer(); for (const change of changes) { switch (change.changeType) { case textModelEvents.RawContentChangedType.LinesInserted: { @@ -271,15 +271,15 @@ export class ViewModel extends Disposable implements IViewModel { switch (change.changeType) { case textModelEvents.RawContentChangedType.Flush: { - this.lines.onModelFlushed(); + this._lines.onModelFlushed(); eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); - this.decorations.reset(); + this._decorations.reset(); this.viewLayout.onFlushed(this.getLineCount()); hadOtherModelChange = true; break; } case textModelEvents.RawContentChangedType.LinesDeleted: { - const linesDeletedEvent = this.lines.onModelLinesDeleted(versionId, change.fromLineNumber, change.toLineNumber); + const linesDeletedEvent = this._lines.onModelLinesDeleted(versionId, change.fromLineNumber, change.toLineNumber); if (linesDeletedEvent !== null) { eventsCollector.emitViewEvent(linesDeletedEvent); this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber); @@ -291,7 +291,7 @@ export class ViewModel extends Disposable implements IViewModel { const insertedLineBreaks = lineBreaks.slice(lineBreaksOffset, lineBreaksOffset + change.detail.length); lineBreaksOffset += change.detail.length; - const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks); + const linesInsertedEvent = this._lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks); if (linesInsertedEvent !== null) { eventsCollector.emitViewEvent(linesInsertedEvent); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); @@ -303,7 +303,7 @@ export class ViewModel extends Disposable implements IViewModel { const changedLineBreakData = lineBreaks[lineBreaksOffset]; lineBreaksOffset++; - const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData); + const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this._lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData); hadModelLineChangeThatChangedLineMapping = lineMappingChanged; if (linesChangedEvent) { eventsCollector.emitViewEvent(linesChangedEvent); @@ -324,37 +324,37 @@ export class ViewModel extends Disposable implements IViewModel { } } } - this.lines.acceptVersionId(versionId); + this._lines.acceptVersionId(versionId); this.viewLayout.onHeightMaybeChanged(); if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) { eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); - this.cursor.onLineMappingChanged(eventsCollector); - this.decorations.onLineMappingChanged(); + this._cursor.onLineMappingChanged(eventsCollector); + this._decorations.onLineMappingChanged(); } } finally { this._eventDispatcher.endEmitViewEvents(); } // Update the configuration and reset the centered view line - this.viewportStartLine = -1; - this.configuration.setMaxLineNumber(this.model.getLineCount()); + this._viewportStartLine = -1; + this._configuration.setMaxLineNumber(this.model.getLineCount()); this._updateConfigurationViewLineCountNow(); // Recover viewport - if (!this.hasFocus && this.model.getAttachedEditorCount() >= 2 && this.viewportStartLineTrackedRange) { - const modelRange = this.model._getTrackedRange(this.viewportStartLineTrackedRange); + if (!this._hasFocus && this.model.getAttachedEditorCount() >= 2 && this._viewportStartLineTrackedRange) { + const modelRange = this.model._getTrackedRange(this._viewportStartLineTrackedRange); if (modelRange) { const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelRange.getStartPosition()); const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); - this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this.viewportStartLineDelta }, ScrollType.Immediate); + this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this._viewportStartLineDelta }, ScrollType.Immediate); } } try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); - this.cursor.onModelContentChanged(eventsCollector, e); + this._cursor.onModelContentChanged(eventsCollector, e); } finally { this._eventDispatcher.endEmitViewEvents(); } @@ -380,25 +380,25 @@ export class ViewModel extends Disposable implements IViewModel { this._register(this.model.onDidChangeLanguageConfiguration((e) => { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent()); - this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); - this.cursor.updateConfiguration(this.cursorConfig); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this._configuration); + this._cursor.updateConfiguration(this.cursorConfig); })); this._register(this.model.onDidChangeLanguage((e) => { - this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); - this.cursor.updateConfiguration(this.cursorConfig); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this._configuration); + this._cursor.updateConfiguration(this.cursorConfig); })); this._register(this.model.onDidChangeOptions((e) => { // A tab size change causes a line mapping changed event => all view parts will repaint OK, no further event needed here - if (this.lines.setTabSize(this.model.getOptions().tabSize)) { + if (this._lines.setTabSize(this.model.getOptions().tabSize)) { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); - this.cursor.onLineMappingChanged(eventsCollector); - this.decorations.onLineMappingChanged(); + this._cursor.onLineMappingChanged(eventsCollector); + this._decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); } finally { this._eventDispatcher.endEmitViewEvents(); @@ -406,12 +406,12 @@ export class ViewModel extends Disposable implements IViewModel { this._updateConfigurationViewLineCount.schedule(); } - this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); - this.cursor.updateConfiguration(this.cursorConfig); + this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this._configuration); + this._cursor.updateConfiguration(this.cursorConfig); })); this._register(this.model.onDidChangeDecorations((e) => { - this.decorations.onModelDecorationsChanged(); + this._decorations.onModelDecorationsChanged(); this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e)); })); } @@ -419,13 +419,13 @@ export class ViewModel extends Disposable implements IViewModel { public setHiddenAreas(ranges: Range[]): void { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); - let lineMappingChanged = this.lines.setHiddenAreas(ranges); + let lineMappingChanged = this._lines.setHiddenAreas(ranges); if (lineMappingChanged) { eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); - this.cursor.onLineMappingChanged(eventsCollector); - this.decorations.onLineMappingChanged(); + this._cursor.onLineMappingChanged(eventsCollector); + this._decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); this.viewLayout.onHeightMaybeChanged(); } @@ -436,8 +436,8 @@ export class ViewModel extends Disposable implements IViewModel { } public getVisibleRangesPlusViewportAboveBelow(): Range[] { - const layoutInfo = this.configuration.options.get(EditorOption.layoutInfo); - const lineHeight = this.configuration.options.get(EditorOption.lineHeight); + const layoutInfo = this._configuration.options.get(EditorOption.layoutInfo); + const lineHeight = this._configuration.options.get(EditorOption.lineHeight); const linesAround = Math.max(20, Math.round(layoutInfo.height / lineHeight)); const partialData = this.viewLayout.getLinesViewportData(); const startViewLineNumber = Math.max(1, partialData.completelyVisibleStartLineNumber - linesAround); @@ -456,7 +456,7 @@ export class ViewModel extends Disposable implements IViewModel { private _toModelVisibleRanges(visibleViewRange: Range): Range[] { const visibleRange = this.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange); - const hiddenAreas = this.lines.getHiddenAreas(); + const hiddenAreas = this._lines.getHiddenAreas(); if (hiddenAreas.length === 0) { return [visibleRange]; @@ -566,43 +566,43 @@ export class ViewModel extends Disposable implements IViewModel { } public getLineCount(): number { - return this.lines.getViewLineCount(); + return this._lines.getViewLineCount(); } /** * Gives a hint that a lot of requests are about to come in for these line numbers. */ public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void { - this.viewportStartLine = startLineNumber; + this._viewportStartLine = startLineNumber; let position = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, this.getLineMinColumn(startLineNumber))); - this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); + this._viewportStartLineTrackedRange = this.model._setTrackedRange(this._viewportStartLineTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); const viewportStartLineTop = this.viewLayout.getVerticalOffsetForLineNumber(startLineNumber); const scrollTop = this.viewLayout.getCurrentScrollTop(); - this.viewportStartLineDelta = scrollTop - viewportStartLineTop; + this._viewportStartLineDelta = scrollTop - viewportStartLineTop; } public getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo { - return this.lines.getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber); + return this._lines.getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber); } public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] { - return this.lines.getViewLinesIndentGuides(startLineNumber, endLineNumber); + return this._lines.getViewLinesIndentGuides(startLineNumber, endLineNumber); } public getLineContent(lineNumber: number): string { - return this.lines.getViewLineContent(lineNumber); + return this._lines.getViewLineContent(lineNumber); } public getLineLength(lineNumber: number): number { - return this.lines.getViewLineLength(lineNumber); + return this._lines.getViewLineLength(lineNumber); } public getLineMinColumn(lineNumber: number): number { - return this.lines.getViewLineMinColumn(lineNumber); + return this._lines.getViewLineMinColumn(lineNumber); } public getLineMaxColumn(lineNumber: number): number { - return this.lines.getViewLineMaxColumn(lineNumber); + return this._lines.getViewLineMaxColumn(lineNumber); } public getLineFirstNonWhitespaceColumn(lineNumber: number): number { @@ -622,15 +622,15 @@ export class ViewModel extends Disposable implements IViewModel { } public getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[] { - return this.decorations.getDecorationsViewportData(visibleRange).decorations; + return this._decorations.getDecorationsViewportData(visibleRange).decorations; } public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData { let mightContainRTL = this.model.mightContainRTL(); let mightContainNonBasicASCII = this.model.mightContainNonBasicASCII(); let tabSize = this.getTabSize(); - let lineData = this.lines.getViewLineData(lineNumber); - let allInlineDecorations = this.decorations.getDecorationsViewportData(visibleRange).inlineDecorations; + let lineData = this._lines.getViewLineData(lineNumber); + let allInlineDecorations = this._decorations.getDecorationsViewportData(visibleRange).inlineDecorations; let inlineDecorations = allInlineDecorations[lineNumber - visibleRange.startLineNumber]; return new ViewLineRenderingData( @@ -648,11 +648,11 @@ export class ViewModel extends Disposable implements IViewModel { } public getViewLineData(lineNumber: number): ViewLineData { - return this.lines.getViewLineData(lineNumber); + return this._lines.getViewLineData(lineNumber); } public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData { - let result = this.lines.getViewLinesData(startLineNumber, endLineNumber, needed); + let result = this._lines.getViewLinesData(startLineNumber, endLineNumber, needed); return new MinimapLinesRenderingData( this.getTabSize(), result @@ -660,7 +660,7 @@ export class ViewModel extends Disposable implements IViewModel { } public getAllOverviewRulerDecorations(theme: EditorTheme): IOverviewRulerDecorations { - return this.lines.getAllOverviewRulerDecorations(this.editorId, filterValidationDecorations(this.configuration.options), theme); + return this._lines.getAllOverviewRulerDecorations(this._editorId, filterValidationDecorations(this._configuration.options), theme); } public invalidateOverviewRulerColorCache(): void { @@ -802,7 +802,7 @@ export class ViewModel extends Disposable implements IViewModel { range = new Range(lineNumber, this.model.getLineMinColumn(lineNumber), lineNumber, this.model.getLineMaxColumn(lineNumber)); } - const fontInfo = this.configuration.options.get(EditorOption.fontInfo); + const fontInfo = this._configuration.options.get(EditorOption.fontInfo); const colorMap = this._getColorMap(); const fontFamily = fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily ? fontInfo.fontFamily : `'${fontInfo.fontFamily}', ${EDITOR_FONT_DEFAULTS.fontFamily}`; @@ -872,50 +872,50 @@ export class ViewModel extends Disposable implements IViewModel { //#region cursor operations public getPrimaryCursorState(): CursorState { - return this.cursor.getPrimaryCursorState(); + return this._cursor.getPrimaryCursorState(); } public getLastAddedCursorIndex(): number { - return this.cursor.getLastAddedCursorIndex(); + return this._cursor.getLastAddedCursorIndex(); } public getCursorStates(): CursorState[] { - return this.cursor.getCursorStates(); + return this._cursor.getCursorStates(); } public setCursorStates(source: string | null | undefined, reason: CursorChangeReason, states: PartialCursorState[] | null): void { - this._withViewEventsCollector(eventsCollector => this.cursor.setStates(eventsCollector, source, reason, states)); + this._withViewEventsCollector(eventsCollector => this._cursor.setStates(eventsCollector, source, reason, states)); } public getCursorColumnSelectData(): IColumnSelectData { - return this.cursor.getCursorColumnSelectData(); + return this._cursor.getCursorColumnSelectData(); } public setCursorColumnSelectData(columnSelectData: IColumnSelectData): void { - this.cursor.setCursorColumnSelectData(columnSelectData); + this._cursor.setCursorColumnSelectData(columnSelectData); } public getPrevEditOperationType(): EditOperationType { - return this.cursor.getPrevEditOperationType(); + return this._cursor.getPrevEditOperationType(); } public setPrevEditOperationType(type: EditOperationType): void { - this.cursor.setPrevEditOperationType(type); + this._cursor.setPrevEditOperationType(type); } public getSelection(): Selection { - return this.cursor.getSelection(); + return this._cursor.getSelection(); } public getSelections(): Selection[] { - return this.cursor.getSelections(); + return this._cursor.getSelections(); } public getPosition(): Position { - return this.cursor.getPrimaryCursorState().modelState.position; + return this._cursor.getPrimaryCursorState().modelState.position; } public setSelections(source: string | null | undefined, selections: readonly ISelection[]): void { - this._withViewEventsCollector(eventsCollector => this.cursor.setSelections(eventsCollector, source, selections)); + this._withViewEventsCollector(eventsCollector => this._cursor.setSelections(eventsCollector, source, selections)); } public saveCursorState(): ICursorState[] { - return this.cursor.saveState(); + return this._cursor.saveState(); } public restoreCursorState(states: ICursorState[]): void { - this._withViewEventsCollector(eventsCollector => this.cursor.restoreState(eventsCollector, states)); + this._withViewEventsCollector(eventsCollector => this._cursor.restoreState(eventsCollector, states)); } private _executeCursorEdit(callback: (eventsCollector: ViewModelEventsCollector) => void): void { - if (this.cursor.context.cursorConfig.readOnly) { + if (this._cursor.context.cursorConfig.readOnly) { // we cannot edit when read only... this._eventDispatcher.emitOutgoingEvent(new ReadOnlyEditAttemptEvent()); return; @@ -923,44 +923,44 @@ export class ViewModel extends Disposable implements IViewModel { this._withViewEventsCollector(callback); } public executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): void { - this._executeCursorEdit(eventsCollector => this.cursor.executeEdits(eventsCollector, source, edits, cursorStateComputer)); + this._executeCursorEdit(eventsCollector => this._cursor.executeEdits(eventsCollector, source, edits, cursorStateComputer)); } public startComposition(): void { - this.cursor.setIsDoingComposition(true); - this._executeCursorEdit(eventsCollector => this.cursor.startComposition(eventsCollector)); + this._cursor.setIsDoingComposition(true); + this._executeCursorEdit(eventsCollector => this._cursor.startComposition(eventsCollector)); } public endComposition(source?: string | null | undefined): void { - this.cursor.setIsDoingComposition(false); - this._executeCursorEdit(eventsCollector => this.cursor.endComposition(eventsCollector, source)); + this._cursor.setIsDoingComposition(false); + this._executeCursorEdit(eventsCollector => this._cursor.endComposition(eventsCollector, source)); } public type(text: string, source?: string | null | undefined): void { - this._executeCursorEdit(eventsCollector => this.cursor.type(eventsCollector, text, source)); + this._executeCursorEdit(eventsCollector => this._cursor.type(eventsCollector, text, source)); } public replacePreviousChar(text: string, replaceCharCnt: number, source?: string | null | undefined): void { - this._executeCursorEdit(eventsCollector => this.cursor.replacePreviousChar(eventsCollector, text, replaceCharCnt, source)); + this._executeCursorEdit(eventsCollector => this._cursor.replacePreviousChar(eventsCollector, text, replaceCharCnt, source)); } public paste(text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void { - this._executeCursorEdit(eventsCollector => this.cursor.paste(eventsCollector, text, pasteOnNewLine, multicursorText, source)); + this._executeCursorEdit(eventsCollector => this._cursor.paste(eventsCollector, text, pasteOnNewLine, multicursorText, source)); } public cut(source?: string | null | undefined): void { - this._executeCursorEdit(eventsCollector => this.cursor.cut(eventsCollector, source)); + this._executeCursorEdit(eventsCollector => this._cursor.cut(eventsCollector, source)); } public executeCommand(command: ICommand, source?: string | null | undefined): void { - this._executeCursorEdit(eventsCollector => this.cursor.executeCommand(eventsCollector, command, source)); + this._executeCursorEdit(eventsCollector => this._cursor.executeCommand(eventsCollector, command, source)); } public executeCommands(commands: ICommand[], source?: string | null | undefined): void { - this._executeCursorEdit(eventsCollector => this.cursor.executeCommands(eventsCollector, commands, source)); + this._executeCursorEdit(eventsCollector => this._cursor.executeCommands(eventsCollector, commands, source)); } public revealPrimaryCursor(source: string | null | undefined, revealHorizontal: boolean): void { - this._withViewEventsCollector(eventsCollector => this.cursor.revealPrimary(eventsCollector, source, revealHorizontal, ScrollType.Smooth)); + this._withViewEventsCollector(eventsCollector => this._cursor.revealPrimary(eventsCollector, source, revealHorizontal, ScrollType.Smooth)); } public revealTopMostCursor(source: string | null | undefined): void { - const viewPosition = this.cursor.getTopMostViewPosition(); + const viewPosition = this._cursor.getTopMostViewPosition(); const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth))); } public revealBottomMostCursor(source: string | null | undefined): void { - const viewPosition = this.cursor.getBottomMostViewPosition(); + const viewPosition = this._cursor.getBottomMostViewPosition(); const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth))); } From 3a26c32c53ca36a43e9100c854c23d3bf8785e12 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 12:01:06 +0200 Subject: [PATCH 16/21] Fix tests --- src/vs/editor/test/browser/controller/cursor.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index d979c8cac5e..532063eff69 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -796,8 +796,6 @@ suite('Editor Controller - Cursor', () => { if (e.kind === OutgoingViewModelEventKind.CursorStateChanged) { events++; assert.deepEqual(e.selections, [new Selection(1, 2, 1, 2)]); - } else { - assert.ok(false); } }); moveTo(editor, viewModel, 1, 2); @@ -812,8 +810,6 @@ suite('Editor Controller - Cursor', () => { if (e.kind === OutgoingViewModelEventKind.CursorStateChanged) { events++; assert.deepEqual(e.selections, [new Selection(1, 1, 1, 2)]); - } else { - assert.ok(false); } }); moveTo(editor, viewModel, 1, 2, true); From 8411186e38540c9d6954cb12d6ff550c22705b39 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 12:03:26 +0200 Subject: [PATCH 17/21] :lipstick: --- src/vs/editor/browser/view/viewImpl.ts | 199 ++++++++++++------------- 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index f57574a6906..1e156722710 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -63,27 +63,27 @@ export interface IOverlayWidgetData { export class View extends ViewEventHandler { - private _scrollbar: EditorScrollbar; + private readonly _scrollbar: EditorScrollbar; private readonly _context: ViewContext; private _selections: Selection[]; // The view lines - private viewLines: ViewLines; + private readonly _viewLines: ViewLines; // These are parts, but we must do some API related calls on them, so we keep a reference - private viewZones: ViewZones; - private contentWidgets: ViewContentWidgets; - private overlayWidgets: ViewOverlayWidgets; - private viewCursors: ViewCursors; - private viewParts: ViewPart[]; + private readonly _viewZones: ViewZones; + private readonly _contentWidgets: ViewContentWidgets; + private readonly _overlayWidgets: ViewOverlayWidgets; + private readonly _viewCursors: ViewCursors; + private readonly _viewParts: ViewPart[]; private readonly _textAreaHandler: TextAreaHandler; - private readonly pointerHandler: PointerHandler; + private readonly _pointerHandler: PointerHandler; // Dom nodes - private linesContent: FastDomNode; - public domNode: FastDomNode; - private overflowGuardContainer: FastDomNode; + private readonly _linesContent: FastDomNode; + public readonly domNode: FastDomNode; + private readonly _overflowGuardContainer: FastDomNode; // Actual mutable state private _renderAnimationFrame: IDisposable | null; @@ -113,53 +113,53 @@ export class View extends ViewEventHandler { this.render(true, false); })); - this.viewParts = []; + this._viewParts = []; // Keyboard handler - this._textAreaHandler = new TextAreaHandler(this._context, viewController, this.createTextAreaHandlerHelper()); - this.viewParts.push(this._textAreaHandler); + this._textAreaHandler = new TextAreaHandler(this._context, viewController, this._createTextAreaHandlerHelper()); + this._viewParts.push(this._textAreaHandler); // These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.) - this.linesContent = createFastDomNode(document.createElement('div')); - this.linesContent.setClassName('lines-content' + ' monaco-editor-background'); - this.linesContent.setPosition('absolute'); + this._linesContent = createFastDomNode(document.createElement('div')); + this._linesContent.setClassName('lines-content' + ' monaco-editor-background'); + this._linesContent.setPosition('absolute'); this.domNode = createFastDomNode(document.createElement('div')); - this.domNode.setClassName(this.getEditorClassName()); + this.domNode.setClassName(this._getEditorClassName()); // Set role 'code' for better screen reader support https://github.com/microsoft/vscode/issues/93438 this.domNode.setAttribute('role', 'code'); - this.overflowGuardContainer = createFastDomNode(document.createElement('div')); - PartFingerprints.write(this.overflowGuardContainer, PartFingerprint.OverflowGuard); - this.overflowGuardContainer.setClassName('overflow-guard'); + this._overflowGuardContainer = createFastDomNode(document.createElement('div')); + PartFingerprints.write(this._overflowGuardContainer, PartFingerprint.OverflowGuard); + this._overflowGuardContainer.setClassName('overflow-guard'); - this._scrollbar = new EditorScrollbar(this._context, this.linesContent, this.domNode, this.overflowGuardContainer); - this.viewParts.push(this._scrollbar); + this._scrollbar = new EditorScrollbar(this._context, this._linesContent, this.domNode, this._overflowGuardContainer); + this._viewParts.push(this._scrollbar); // View Lines - this.viewLines = new ViewLines(this._context, this.linesContent); + this._viewLines = new ViewLines(this._context, this._linesContent); // View Zones - this.viewZones = new ViewZones(this._context); - this.viewParts.push(this.viewZones); + this._viewZones = new ViewZones(this._context); + this._viewParts.push(this._viewZones); // Decorations overview ruler const decorationsOverviewRuler = new DecorationsOverviewRuler(this._context); - this.viewParts.push(decorationsOverviewRuler); + this._viewParts.push(decorationsOverviewRuler); const scrollDecoration = new ScrollDecorationViewPart(this._context); - this.viewParts.push(scrollDecoration); + this._viewParts.push(scrollDecoration); const contentViewOverlays = new ContentViewOverlays(this._context); - this.viewParts.push(contentViewOverlays); + this._viewParts.push(contentViewOverlays); contentViewOverlays.addDynamicOverlay(new CurrentLineHighlightOverlay(this._context)); contentViewOverlays.addDynamicOverlay(new SelectionsOverlay(this._context)); contentViewOverlays.addDynamicOverlay(new IndentGuidesOverlay(this._context)); contentViewOverlays.addDynamicOverlay(new DecorationsOverlay(this._context)); const marginViewOverlays = new MarginViewOverlays(this._context); - this.viewParts.push(marginViewOverlays); + this._viewParts.push(marginViewOverlays); marginViewOverlays.addDynamicOverlay(new CurrentLineMarginHighlightOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new GlyphMarginOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new MarginViewLineDecorationsOverlay(this._context)); @@ -167,26 +167,26 @@ export class View extends ViewEventHandler { marginViewOverlays.addDynamicOverlay(new LineNumbersOverlay(this._context)); const margin = new Margin(this._context); - margin.getDomNode().appendChild(this.viewZones.marginDomNode); + margin.getDomNode().appendChild(this._viewZones.marginDomNode); margin.getDomNode().appendChild(marginViewOverlays.getDomNode()); - this.viewParts.push(margin); + this._viewParts.push(margin); // Content widgets - this.contentWidgets = new ViewContentWidgets(this._context, this.domNode); - this.viewParts.push(this.contentWidgets); + this._contentWidgets = new ViewContentWidgets(this._context, this.domNode); + this._viewParts.push(this._contentWidgets); - this.viewCursors = new ViewCursors(this._context); - this.viewParts.push(this.viewCursors); + this._viewCursors = new ViewCursors(this._context); + this._viewParts.push(this._viewCursors); // Overlay widgets - this.overlayWidgets = new ViewOverlayWidgets(this._context); - this.viewParts.push(this.overlayWidgets); + this._overlayWidgets = new ViewOverlayWidgets(this._context); + this._viewParts.push(this._overlayWidgets); const rulers = new Rulers(this._context); - this.viewParts.push(rulers); + this._viewParts.push(rulers); const minimap = new Minimap(this._context); - this.viewParts.push(minimap); + this._viewParts.push(minimap); // -------------- Wire dom nodes up @@ -195,74 +195,74 @@ export class View extends ViewEventHandler { overviewRulerData.parent.insertBefore(decorationsOverviewRuler.getDomNode(), overviewRulerData.insertBefore); } - this.linesContent.appendChild(contentViewOverlays.getDomNode()); - this.linesContent.appendChild(rulers.domNode); - this.linesContent.appendChild(this.viewZones.domNode); - this.linesContent.appendChild(this.viewLines.getDomNode()); - this.linesContent.appendChild(this.contentWidgets.domNode); - this.linesContent.appendChild(this.viewCursors.getDomNode()); - this.overflowGuardContainer.appendChild(margin.getDomNode()); - this.overflowGuardContainer.appendChild(this._scrollbar.getDomNode()); - this.overflowGuardContainer.appendChild(scrollDecoration.getDomNode()); - this.overflowGuardContainer.appendChild(this._textAreaHandler.textArea); - this.overflowGuardContainer.appendChild(this._textAreaHandler.textAreaCover); - this.overflowGuardContainer.appendChild(this.overlayWidgets.getDomNode()); - this.overflowGuardContainer.appendChild(minimap.getDomNode()); - this.domNode.appendChild(this.overflowGuardContainer); - this.domNode.appendChild(this.contentWidgets.overflowingContentWidgetsDomNode); + this._linesContent.appendChild(contentViewOverlays.getDomNode()); + this._linesContent.appendChild(rulers.domNode); + this._linesContent.appendChild(this._viewZones.domNode); + this._linesContent.appendChild(this._viewLines.getDomNode()); + this._linesContent.appendChild(this._contentWidgets.domNode); + this._linesContent.appendChild(this._viewCursors.getDomNode()); + this._overflowGuardContainer.appendChild(margin.getDomNode()); + this._overflowGuardContainer.appendChild(this._scrollbar.getDomNode()); + this._overflowGuardContainer.appendChild(scrollDecoration.getDomNode()); + this._overflowGuardContainer.appendChild(this._textAreaHandler.textArea); + this._overflowGuardContainer.appendChild(this._textAreaHandler.textAreaCover); + this._overflowGuardContainer.appendChild(this._overlayWidgets.getDomNode()); + this._overflowGuardContainer.appendChild(minimap.getDomNode()); + this.domNode.appendChild(this._overflowGuardContainer); + this.domNode.appendChild(this._contentWidgets.overflowingContentWidgetsDomNode); this._applyLayout(); // Pointer handler - this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper())); + this._pointerHandler = this._register(new PointerHandler(this._context, viewController, this._createPointerHandlerHelper())); } private _flushAccumulatedAndRenderNow(): void { this._renderNow(); } - private createPointerHandlerHelper(): IPointerHandlerHelper { + private _createPointerHandlerHelper(): IPointerHandlerHelper { return { viewDomNode: this.domNode.domNode, - linesContentDomNode: this.linesContent.domNode, + linesContentDomNode: this._linesContent.domNode, focusTextArea: () => { this.focus(); }, getLastRenderData: (): PointerHandlerLastRenderData => { - const lastViewCursorsRenderData = this.viewCursors.getLastRenderData() || []; + const lastViewCursorsRenderData = this._viewCursors.getLastRenderData() || []; const lastTextareaPosition = this._textAreaHandler.getLastRenderData(); return new PointerHandlerLastRenderData(lastViewCursorsRenderData, lastTextareaPosition); }, shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => { - return this.viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId); + return this._viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId); }, shouldSuppressMouseDownOnWidget: (widgetId: string) => { - return this.contentWidgets.shouldSuppressMouseDownOnWidget(widgetId); + return this._contentWidgets.shouldSuppressMouseDownOnWidget(widgetId); }, getPositionFromDOMInfo: (spanNode: HTMLElement, offset: number) => { this._flushAccumulatedAndRenderNow(); - return this.viewLines.getPositionFromDOMInfo(spanNode, offset); + return this._viewLines.getPositionFromDOMInfo(spanNode, offset); }, visibleRangeForPosition: (lineNumber: number, column: number) => { this._flushAccumulatedAndRenderNow(); - return this.viewLines.visibleRangeForPosition(new Position(lineNumber, column)); + return this._viewLines.visibleRangeForPosition(new Position(lineNumber, column)); }, getLineWidth: (lineNumber: number) => { this._flushAccumulatedAndRenderNow(); - return this.viewLines.getLineWidth(lineNumber); + return this._viewLines.getLineWidth(lineNumber); } }; } - private createTextAreaHandlerHelper(): ITextAreaHandlerHelper { + private _createTextAreaHandlerHelper(): ITextAreaHandlerHelper { return { visibleRangeForPositionRelativeToEditor: (lineNumber: number, column: number) => { this._flushAccumulatedAndRenderNow(); - return this.viewLines.visibleRangeForPosition(new Position(lineNumber, column)); + return this._viewLines.visibleRangeForPosition(new Position(lineNumber, column)); } }; } @@ -274,14 +274,14 @@ export class View extends ViewEventHandler { this.domNode.setWidth(layoutInfo.width); this.domNode.setHeight(layoutInfo.height); - this.overflowGuardContainer.setWidth(layoutInfo.width); - this.overflowGuardContainer.setHeight(layoutInfo.height); + this._overflowGuardContainer.setWidth(layoutInfo.width); + this._overflowGuardContainer.setHeight(layoutInfo.height); - this.linesContent.setWidth(1000000); - this.linesContent.setHeight(1000000); + this._linesContent.setWidth(1000000); + this._linesContent.setHeight(1000000); } - private getEditorClassName() { + private _getEditorClassName() { const focused = this._textAreaHandler.isFocused() ? ' focused' : ''; return this._context.configuration.options.get(EditorOption.editorClassName) + ' ' + getThemeTypeSelector(this._context.theme.type) + focused; } @@ -292,7 +292,7 @@ export class View extends ViewEventHandler { this._scheduleRender(); } public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - this.domNode.setClassName(this.getEditorClassName()); + this.domNode.setClassName(this._getEditorClassName()); this._applyLayout(); return false; } @@ -301,11 +301,11 @@ export class View extends ViewEventHandler { return false; } public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { - this.domNode.setClassName(this.getEditorClassName()); + this.domNode.setClassName(this._getEditorClassName()); return false; } public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { - this.domNode.setClassName(this.getEditorClassName()); + this.domNode.setClassName(this._getEditorClassName()); return false; } @@ -319,13 +319,12 @@ export class View extends ViewEventHandler { this._context.removeEventHandler(this); - this.viewLines.dispose(); + this._viewLines.dispose(); // Destroy view parts - for (let i = 0, len = this.viewParts.length; i < len; i++) { - this.viewParts[i].dispose(); + for (let i = 0, len = this._viewParts.length; i < len; i++) { + this._viewParts[i].dispose(); } - this.viewParts = []; super.dispose(); } @@ -347,8 +346,8 @@ export class View extends ViewEventHandler { private _getViewPartsToRender(): ViewPart[] { let result: ViewPart[] = [], resultLen = 0; - for (let i = 0, len = this.viewParts.length; i < len; i++) { - const viewPart = this.viewParts[i]; + for (let i = 0, len = this._viewParts.length; i < len; i++) { + const viewPart = this._viewParts[i]; if (viewPart.shouldRender()) { result[resultLen++] = viewPart; } @@ -363,7 +362,7 @@ export class View extends ViewEventHandler { let viewPartsToRender = this._getViewPartsToRender(); - if (!this.viewLines.shouldRender() && viewPartsToRender.length === 0) { + if (!this._viewLines.shouldRender() && viewPartsToRender.length === 0) { // Nothing to render return; } @@ -378,20 +377,20 @@ export class View extends ViewEventHandler { this._context.model ); - if (this.contentWidgets.shouldRender()) { + if (this._contentWidgets.shouldRender()) { // Give the content widgets a chance to set their max width before a possible synchronous layout - this.contentWidgets.onBeforeRender(viewportData); + this._contentWidgets.onBeforeRender(viewportData); } - if (this.viewLines.shouldRender()) { - this.viewLines.renderText(viewportData); - this.viewLines.onDidRender(); + if (this._viewLines.shouldRender()) { + this._viewLines.renderText(viewportData); + this._viewLines.onDidRender(); // Rendering of viewLines might cause scroll events to occur, so collect view parts to render again viewPartsToRender = this._getViewPartsToRender(); } - const renderingContext = new RenderingContext(this._context.viewLayout, viewportData, this.viewLines); + const renderingContext = new RenderingContext(this._context.viewLayout, viewportData, this._viewLines); // Render the rest of the parts for (let i = 0, len = viewPartsToRender.length; i < len; i++) { @@ -416,7 +415,7 @@ export class View extends ViewEventHandler { this._context.model.setScrollPosition({ scrollTop: scrollPosition.scrollTop }, ScrollType.Immediate); this._context.model.tokenizeViewport(); this._renderNow(); - this.viewLines.updateLineWidths(); + this._viewLines.updateLineWidths(); this._context.model.setScrollPosition({ scrollLeft: scrollPosition.scrollLeft }, ScrollType.Immediate); } @@ -427,7 +426,7 @@ export class View extends ViewEventHandler { }); const viewPosition = this._context.model.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); this._flushAccumulatedAndRenderNow(); - const visibleRange = this.viewLines.visibleRangeForPosition(new Position(viewPosition.lineNumber, viewPosition.column)); + const visibleRange = this._viewLines.visibleRangeForPosition(new Position(viewPosition.lineNumber, viewPosition.column)); if (!visibleRange) { return -1; } @@ -435,7 +434,7 @@ export class View extends ViewEventHandler { } public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null { - const mouseTarget = this.pointerHandler.getTargetAtClientPoint(clientX, clientY); + const mouseTarget = this._pointerHandler.getTargetAtClientPoint(clientX, clientY); if (!mouseTarget) { return null; } @@ -447,16 +446,16 @@ export class View extends ViewEventHandler { } public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): void { - this.viewZones.changeViewZones(callback); + this._viewZones.changeViewZones(callback); this._scheduleRender(); } public render(now: boolean, everything: boolean): void { if (everything) { // Force everything to render... - this.viewLines.forceShouldRender(); - for (let i = 0, len = this.viewParts.length; i < len; i++) { - const viewPart = this.viewParts[i]; + this._viewLines.forceShouldRender(); + for (let i = 0, len = this._viewParts.length; i < len; i++) { + const viewPart = this._viewParts[i]; viewPart.forceShouldRender(); } } @@ -484,7 +483,7 @@ export class View extends ViewEventHandler { } public addContentWidget(widgetData: IContentWidgetData): void { - this.contentWidgets.addWidget(widgetData.widget); + this._contentWidgets.addWidget(widgetData.widget); this.layoutContentWidget(widgetData); this._scheduleRender(); } @@ -498,31 +497,31 @@ export class View extends ViewEventHandler { } } const newPreference = widgetData.position ? widgetData.position.preference : null; - this.contentWidgets.setWidgetPosition(widgetData.widget, newRange, newPreference); + this._contentWidgets.setWidgetPosition(widgetData.widget, newRange, newPreference); this._scheduleRender(); } public removeContentWidget(widgetData: IContentWidgetData): void { - this.contentWidgets.removeWidget(widgetData.widget); + this._contentWidgets.removeWidget(widgetData.widget); this._scheduleRender(); } public addOverlayWidget(widgetData: IOverlayWidgetData): void { - this.overlayWidgets.addWidget(widgetData.widget); + this._overlayWidgets.addWidget(widgetData.widget); this.layoutOverlayWidget(widgetData); this._scheduleRender(); } public layoutOverlayWidget(widgetData: IOverlayWidgetData): void { const newPreference = widgetData.position ? widgetData.position.preference : null; - const shouldRender = this.overlayWidgets.setWidgetPosition(widgetData.widget, newPreference); + const shouldRender = this._overlayWidgets.setWidgetPosition(widgetData.widget, newPreference); if (shouldRender) { this._scheduleRender(); } } public removeOverlayWidget(widgetData: IOverlayWidgetData): void { - this.overlayWidgets.removeWidget(widgetData.widget); + this._overlayWidgets.removeWidget(widgetData.widget); this._scheduleRender(); } From 1fd395773f9f09e86fcb78fa3db8aff6f02960c7 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 25 May 2020 12:09:06 +0200 Subject: [PATCH 18/21] Have the view model receive configuration change events first --- src/vs/editor/common/config/commonEditorConfig.ts | 4 ++++ src/vs/editor/common/editorCommon.ts | 1 + src/vs/editor/common/viewModel/viewModelImpl.ts | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 609f97f351d..954af2fa4f2 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -283,6 +283,9 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; + private _onDidChangeFast = this._register(new Emitter()); + public readonly onDidChangeFast: Event = this._onDidChangeFast.event; + public readonly isSimpleWidget: boolean; private _computeOptionsMemory: ComputeOptionsMemory; public options!: ComputedEditorOptions; @@ -334,6 +337,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC } this.options = newOptions; + this._onDidChangeFast.fire(changeEvent); this._onDidChange.fire(changeEvent); } } diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 530cd078d9e..94c4a3c48df 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -150,6 +150,7 @@ export interface ILineChange extends IChange { * @internal */ export interface IConfiguration extends IDisposable { + onDidChangeFast(listener: (e: ConfigurationChangedEvent) => void): IDisposable; onDidChange(listener: (e: ConfigurationChangedEvent) => void): IDisposable; readonly options: IComputedEditorOptions; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index fae83ed8666..f7f130f0866 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -126,7 +126,7 @@ export class ViewModel extends Disposable implements IViewModel { this._registerModelEvents(); - this._register(this._configuration.onDidChange((e) => { + this._register(this._configuration.onDidChangeFast((e) => { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); this._onConfigurationChanged(eventsCollector, e); From 240d1b7312350c130a3c434065f0b2fafad64342 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 May 2020 14:02:09 +0200 Subject: [PATCH 19/21] Fix #98513 --- .../userDataSync/common/extensionsMerge.ts | 178 ++++--- .../userDataSync/common/extensionsSync.ts | 52 +- .../userDataSync/common/userDataSync.ts | 1 + .../test/common/extensionsMerge.test.ts | 500 +++++++++++------- 4 files changed, 441 insertions(+), 290 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index 43f246b5733..7b6a48834fc 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -7,6 +7,7 @@ import { values, keys } from 'vs/base/common/map'; import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { startsWith } from 'vs/base/common/strings'; +import { deepClone } from 'vs/base/common/objects'; export interface IMergeResult { added: ISyncExtension[]; @@ -30,8 +31,6 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync }; } - // massage incoming extension - add disabled property - const massageIncomingExtension = (extension: ISyncExtension): ISyncExtension => ({ ...extension, ...{ disabled: !!extension.disabled } }); localExtensions = localExtensions.map(massageIncomingExtension); remoteExtensions = remoteExtensions.map(massageIncomingExtension); lastSyncExtensions = lastSyncExtensions ? lastSyncExtensions.map(massageIncomingExtension) : null; @@ -54,7 +53,14 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync }; const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map()); const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); - const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); + const newRemoteExtensionsMap = remoteExtensions.reduce((map: Map, extension: ISyncExtension) => { + const key = getKey(extension); + extension = deepClone(extension); + if (localExtensionsMap.get(key)?.installed) { + extension.installed = true; + } + return addExtensionToMap(map, extension); + }, new Map()); const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map()) : null; const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map()); const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => { @@ -63,90 +69,82 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync }, new Set()); const localToRemote = compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet); - if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { - // No changes found between local and remote. - return { added: [], removed: [], updated: [], remote: null }; - } + if (localToRemote.added.size > 0 || localToRemote.removed.size > 0 || localToRemote.updated.size > 0) { - const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet); - const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet); + const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet); + const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet); - // massage outgoing extension - remove disabled property - const massageOutgoingExtension = (extension: ISyncExtension, key: string): ISyncExtension => { - const massagedExtension: ISyncExtension = { - identifier: { - id: extension.identifier.id, - uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined - }, - }; - if (extension.disabled) { - massagedExtension.disabled = true; - } - if (extension.version) { - massagedExtension.version = extension.version; - } - return massagedExtension; - }; - - // Remotely removed extension. - for (const key of values(baseToRemote.removed)) { - const e = localExtensionsMap.get(key); - if (e) { - removed.push(e.identifier); - } - } - - // Remotely added extension - for (const key of values(baseToRemote.added)) { - // Got added in local - if (baseToLocal.added.has(key)) { - // Is different from local to remote - if (localToRemote.updated.has(key)) { - updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key)); + // Remotely removed extension. + for (const key of values(baseToRemote.removed)) { + const e = localExtensionsMap.get(key); + if (e) { + removed.push(e.identifier); } - } else { - // Add to local - added.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key)); - } - } - - // Remotely updated extensions - for (const key of values(baseToRemote.updated)) { - // Update in local always - updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key)); - } - - // Locally added extensions - for (const key of values(baseToLocal.added)) { - // Not there in remote - if (!baseToRemote.added.has(key)) { - newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!); - } - } - - // Locally updated extensions - for (const key of values(baseToLocal.updated)) { - // If removed in remote - if (baseToRemote.removed.has(key)) { - continue; } - // If not updated in remote - if (!baseToRemote.updated.has(key)) { - newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!); + // Remotely added extension + for (const key of values(baseToRemote.added)) { + // Got added in local + if (baseToLocal.added.has(key)) { + // Is different from local to remote + if (localToRemote.updated.has(key)) { + updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key)); + } + } else { + // Add only installed extension to local + const remoteExtension = remoteExtensionsMap.get(key)!; + if (remoteExtension.installed) { + added.push(massageOutgoingExtension(remoteExtension, key)); + } + } } - } - // Locally removed extensions - for (const key of values(baseToLocal.removed)) { - // If not skipped and not updated in remote - if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) { - newRemoteExtensionsMap.delete(key); + // Remotely updated extensions + for (const key of values(baseToRemote.updated)) { + // Update in local always + updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key)); + } + + // Locally added extensions + for (const key of values(baseToLocal.added)) { + // Not there in remote + if (!baseToRemote.added.has(key)) { + newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!); + } + } + + // Locally updated extensions + for (const key of values(baseToLocal.updated)) { + // If removed in remote + if (baseToRemote.removed.has(key)) { + continue; + } + + // If not updated in remote + if (!baseToRemote.updated.has(key)) { + const extension = deepClone(localExtensionsMap.get(key)!); + // Retain installed property + if (newRemoteExtensionsMap.get(key)?.installed) { + extension.installed = true; + } + newRemoteExtensionsMap.set(key, extension); + } + } + + // Locally removed extensions + for (const key of values(baseToLocal.removed)) { + // If not skipped and not updated in remote + if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) { + // Remove only if it is an installed extension + if (lastSyncExtensionsMap?.get(key)?.installed) { + newRemoteExtensionsMap.delete(key); + } + } } } const remote: ISyncExtension[] = []; - const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set()); + const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set(), { checkInstalledProperty: true }); if (remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0) { newRemoteExtensionsMap.forEach((value, key) => remote.push(massageOutgoingExtension(value, key))); } @@ -154,7 +152,7 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync return { added, removed, updated, remote: remote.length ? remote : null }; } -function compare(from: Map | null, to: Map, ignoredExtensions: Set): { added: Set, removed: Set, updated: Set } { +function compare(from: Map | null, to: Map, ignoredExtensions: Set, { checkInstalledProperty }: { checkInstalledProperty: boolean } = { checkInstalledProperty: false }): { added: Set, removed: Set, updated: Set } { const fromKeys = from ? keys(from).filter(key => !ignoredExtensions.has(key)) : []; const toKeys = keys(to).filter(key => !ignoredExtensions.has(key)); const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); @@ -170,6 +168,7 @@ function compare(from: Map | null, to: Map | null, to: Map { const localExtensions = await this.getLocalExtensions(); - const syncExtensions = this.parseExtensions(syncData); + const syncExtensions = await this.parseAndMigrateExtensions(syncData); const { added, updated, removed } = merge(localExtensions, syncExtensions, localExtensions, [], this.getIgnoredExtensions()); await this.apply({ @@ -215,8 +218,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { - const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? this.parseExtensions(remoteUserData.syncData) : null; - const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? this.parseExtensions(lastSyncUserData.syncData!) : null; + const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null; + const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await this.parseAndMigrateExtensions(lastSyncUserData.syncData!) : null; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; const localExtensions = await this.getLocalExtensions(); @@ -353,31 +356,50 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return newSkippedExtensions; } - private parseExtensions(syncData: ISyncData): ISyncExtension[] { - let extensions: ISyncExtension[] = JSON.parse(syncData.content); - if (syncData.version === 1) { - extensions = extensions.map(e => { + private async parseAndMigrateExtensions(syncData: ISyncData): Promise { + const extensions = this.parseExtensions(syncData); + if (syncData.version === 1 + || syncData.version === 2 + ) { + const systemExtensions = await this.extensionManagementService.getInstalled(ExtensionType.System); + for (const extension of extensions) { // #region Migration from v1 (enabled -> disabled) - if ((e).enabled === false) { - e.disabled = true; + if (syncData.version === 1) { + if ((extension).enabled === false) { + extension.disabled = true; + } + delete (extension).enabled; } - delete (e).enabled; // #endregion - return e; - }); + + // #region Migration from v2 (set installed property on extension) + if (syncData.version === 2) { + if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) { + extension.installed = true; + } + } + // #endregion + } } return extensions; } + private parseExtensions(syncData: ISyncData): ISyncExtension[] { + return JSON.parse(syncData.content); + } + private async getLocalExtensions(): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); const disabledExtensions = this.extensionEnablementService.getDisabledExtensions(); return installedExtensions - .map(({ identifier }) => { + .map(({ identifier, type }) => { const syncExntesion: ISyncExtension = { identifier }; if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { syncExntesion.disabled = true; } + if (type === ExtensionType.User) { + syncExntesion.installed = true; + } return syncExntesion; }); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 79ac028cd0d..54b11c9f53b 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -239,6 +239,7 @@ export interface ISyncExtension { identifier: IExtensionIdentifier; version?: string; disabled?: boolean; + installed?: boolean; } export interface IStorageValue { diff --git a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index 3bd7057806f..a66b9d45f8c 100644 --- a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -7,13 +7,13 @@ import * as assert from 'assert'; import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; -suite('ExtensionsMerge - No Conflicts', () => { +suite('ExtensionsMerge', () => { - test('merge returns local extension if remote does not exist', async () => { + test('merge returns local extension if remote does not exist', () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, null, null, [], []); @@ -24,15 +24,15 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, localExtensions); }); - test('merge returns local extension if remote does not exist with ignored extensions', async () => { + test('merge returns local extension if remote does not exist with ignored extensions', () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, null, null, [], ['a']); @@ -43,15 +43,15 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, expected); }); - test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', async () => { + test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, null, null, [], ['A']); @@ -62,19 +62,19 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, expected); }); - test('merge returns local extension if remote does not exist with skipped extensions', async () => { + test('merge returns local extension if remote does not exist with skipped extensions', () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const skippedExtension: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, null, null, skippedExtension, []); @@ -85,18 +85,18 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, expected); }); - test('merge returns local extension if remote does not exist with skipped and ignored extensions', async () => { + test('merge returns local extension if remote does not exist with skipped and ignored extensions', () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const skippedExtension: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, null, null, skippedExtension, ['a']); @@ -107,180 +107,180 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, expected); }); - test('merge local and remote extensions when there is no base', async () => { + test('merge local and remote extensions when there is no base', () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]); assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); }); - test('merge local and remote extensions when there is no base and with ignored extensions', async () => { + test('merge local and remote extensions when there is no base and with ignored extensions', () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], ['a']); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]); assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); }); - test('merge local and remote extensions when remote is moved forwarded', async () => { + test('merge local and remote extensions when remote is moved forwarded', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]); assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]); assert.deepEqual(actual.updated, []); assert.equal(actual.remote, null); }); - test('merge local and remote extensions when remote is moved forwarded with disabled extension', async () => { + test('merge local and remote extensions when remote is moved forwarded with disabled extension', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, - { identifier: { id: 'd', uuid: 'd' }, disabled: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]); assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]); - assert.deepEqual(actual.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true }]); + assert.deepEqual(actual.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true }]); assert.equal(actual.remote, null); }); - test('merge local and remote extensions when remote moved forwarded with ignored extensions', async () => { + test('merge local and remote extensions when remote moved forwarded with ignored extensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a']); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]); assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); assert.deepEqual(actual.updated, []); assert.equal(actual.remote, null); }); - test('merge local and remote extensions when remote is moved forwarded with skipped extensions', async () => { + test('merge local and remote extensions when remote is moved forwarded with skipped extensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]); assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); assert.deepEqual(actual.updated, []); assert.equal(actual.remote, null); }); - test('merge local and remote extensions when remote is moved forwarded with skipped and ignored extensions', async () => { + test('merge local and remote extensions when remote is moved forwarded with skipped and ignored extensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']); - assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, installed: true }]); assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); assert.deepEqual(actual.updated, []); assert.equal(actual.remote, null); }); - test('merge local and remote extensions when local is moved forwarded', async () => { + test('merge local and remote extensions when local is moved forwarded', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); @@ -291,19 +291,19 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, localExtensions); }); - test('merge local and remote extensions when local is moved forwarded with disabled extensions', async () => { + test('merge local and remote extensions when local is moved forwarded with disabled extensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, disabled: true }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, disabled: true, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); @@ -314,18 +314,18 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, localExtensions); }); - test('merge local and remote extensions when local is moved forwarded with ignored settings', async () => { + test('merge local and remote extensions when local is moved forwarded with ignored settings', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']); @@ -334,30 +334,30 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, [ - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]); }); - test('merge local and remote extensions when local is moved forwarded with skipped extensions', async () => { + test('merge local and remote extensions when local is moved forwarded with skipped extensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); @@ -368,25 +368,25 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, expected); }); - test('merge local and remote extensions when local is moved forwarded with skipped and ignored extensions', async () => { + test('merge local and remote extensions when local is moved forwarded with skipped and ignored extensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, - { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']); @@ -397,54 +397,54 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, expected); }); - test('merge local and remote extensions when both moved forwarded', async () => { + test('merge local and remote extensions when both moved forwarded', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'e', uuid: 'e' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'e', uuid: 'e' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'e', uuid: 'e' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'e', uuid: 'e' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true }]); assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); }); - test('merge local and remote extensions when both moved forwarded with ignored extensions', async () => { + test('merge local and remote extensions when both moved forwarded with ignored extensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'e', uuid: 'e' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'e', uuid: 'e' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'e', uuid: 'e' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'e', uuid: 'e' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']); @@ -455,58 +455,58 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, expected); }); - test('merge local and remote extensions when both moved forwarded with skipped extensions', async () => { + test('merge local and remote extensions when both moved forwarded with skipped extensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'e', uuid: 'e' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'e', uuid: 'e' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'e', uuid: 'e' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'e', uuid: 'e' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true }]); assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); }); - test('merge local and remote extensions when both moved forwarded with skipped and ignoredextensions', async () => { + test('merge local and remote extensions when both moved forwarded with skipped and ignoredextensions', () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'e', uuid: 'e' } }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'e', uuid: 'e' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'e', uuid: 'e' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'e', uuid: 'e' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']); @@ -517,30 +517,134 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.remote, expected); }); - test('merge when remote extension has no uuid and different extension id case', async () => { + test('merge when remote extension has no uuid and different extension id case', () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'A' } }, - { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'A' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'A', uuid: 'a' } }, - { identifier: { id: 'd', uuid: 'd' } }, - { identifier: { id: 'b', uuid: 'b' } }, - { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'A', uuid: 'a' }, installed: true }, + { identifier: { id: 'd', uuid: 'd' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' }, installed: true }, + { identifier: { id: 'c', uuid: 'c' }, installed: true }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' } }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, installed: true }]); assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); }); + test('merge when remote extension is not an installed extension', () => { + const localExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + ]; + const remoteExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + { identifier: { id: 'b', uuid: 'b' } }, + ]; + + const actual = merge(localExtensions, remoteExtensions, null, [], []); + + assert.deepEqual(actual.added, []); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.updated, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when remote extension is not an installed extension but is an installed extension locally', () => { + const localExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + ]; + const remoteExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + ]; + + const actual = merge(localExtensions, remoteExtensions, null, [], []); + + assert.deepEqual(actual.added, []); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.updated, []); + assert.deepEqual(actual.remote, localExtensions); + }); + + test('merge when an extension is not an installed extension remotely and does not exist locally', () => { + const localExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + ]; + const remoteExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + ]; + + const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []); + + assert.deepEqual(actual.added, []); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.updated, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when an extension is an installed extension remotely but not locally and updated locally', () => { + const localExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' }, disabled: true }, + ]; + const remoteExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' }, installed: true }, + ]; + const expected: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []); + + assert.deepEqual(actual.added, []); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.updated, []); + assert.deepEqual(actual.remote, expected); + }); + + test('merge when an extension is an installed extension remotely but not locally and updated remotely', () => { + const localExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + ]; + const remoteExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, localExtensions, [], []); + + assert.deepEqual(actual.added, []); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.updated, remoteExtensions); + assert.deepEqual(actual.remote, null); + }); + + test('merge not installed extensions', () => { + const localExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + ]; + const remoteExtensions: ISyncExtension[] = [ + { identifier: { id: 'b', uuid: 'b' } }, + ]; + const expected: ISyncExtension[] = [ + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'a', uuid: 'a' } }, + ]; + + const actual = merge(localExtensions, remoteExtensions, null, [], []); + + assert.deepEqual(actual.added, []); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.updated, []); + assert.deepEqual(actual.remote, expected); + }); }); From 230a2293df16f97fe73800ca4605c19ddf284a85 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 25 May 2020 14:32:43 +0200 Subject: [PATCH 20/21] Fix race condition in getting task execution Fixes #96643 --- src/vs/workbench/api/common/extHostTask.ts | 26 +++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 71430153a3d..27759c2666f 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -384,6 +384,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { protected _handleCounter: number; protected _handlers: Map; protected _taskExecutions: Map; + protected _taskExecutionPromises: Map>; protected _providedCustomExecutions2: Map; private _notProvidedCustomExecutions: Set; // Used for custom executions tasks that are created and run through executeTask. protected _activeCustomExecutions2: Map; @@ -412,6 +413,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._handleCounter = 0; this._handlers = new Map(); this._taskExecutions = new Map(); + this._taskExecutionPromises = new Map>(); this._providedCustomExecutions2 = new Map(); this._notProvidedCustomExecutions = new Set(); this._activeCustomExecutions2 = new Map(); @@ -496,6 +498,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { public async $OnDidEndTask(execution: tasks.TaskExecutionDTO): Promise { const _execution = await this.getTaskExecution(execution); + this._taskExecutionPromises.delete(execution.id); this._taskExecutions.delete(execution.id); this.customExecutionComplete(execution); this._onDidTerminateTask.fire({ @@ -626,17 +629,24 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { return taskExecution; } - let result: TaskExecutionImpl | undefined = this._taskExecutions.get(execution.id); + let result: Promise | undefined = this._taskExecutionPromises.get(execution.id); if (result) { return result; } - const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider); - if (!taskToCreate) { - throw new Error('Unexpected: Task does not exist.'); - } - const createdResult: TaskExecutionImpl = new TaskExecutionImpl(this, execution.id, taskToCreate); - this._taskExecutions.set(execution.id, createdResult); - return createdResult; + const createdResult: Promise = new Promise(async (resolve, reject) => { + const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider); + if (!taskToCreate) { + reject('Unexpected: Task does not exist.'); + } else { + resolve(new TaskExecutionImpl(this, execution.id, taskToCreate)); + } + }); + + this._taskExecutionPromises.set(execution.id, createdResult); + return createdResult.then(result => { + this._taskExecutions.set(execution.id, result); + return result; + }); } protected checkDeprecation(task: vscode.Task, handler: HandlerData) { From 5ccb199a2952fda7d39966a1516446a7ba44cf04 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 25 May 2020 15:10:31 +0200 Subject: [PATCH 21/21] Remove location specifc color in remote explorer Fixes #95705 --- src/vs/workbench/contrib/remote/browser/explorerViewItems.ts | 5 +---- .../workbench/contrib/remote/browser/media/remoteViewlet.css | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts index 536993873b4..9223c6d4b94 100644 --- a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -11,7 +11,6 @@ import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { IRemoteExplorerService, REMOTE_EXPLORER_TYPE_KEY } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; @@ -40,9 +39,7 @@ export class SwitchRemoteViewItem extends SelectActionViewItem { @IStorageService private readonly storageService: IStorageService ) { super(null, action, optionsItems, 0, contextViewService, { ariaLabel: nls.localize('remotes', 'Switch Remote') }); - this._register(attachSelectBoxStyler(this.selectBox, themeService, { - selectBackground: SIDE_BAR_BACKGROUND - })); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); this.setSelectionForConnection(optionsItems, environmentService, remoteExplorerService); } diff --git a/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css index f9b9d5cc1b8..d6709d5d8d9 100644 --- a/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css @@ -57,5 +57,5 @@ } .monaco-workbench .part > .title > .title-actions .switch-remote > .monaco-select-box { - padding: 0 22px 0 6px; + padding: 1px 22px 2px 6px; }