diff --git a/src/vs/editor/browser/observableUtilities.ts b/src/vs/editor/browser/observableUtilities.ts index 22be6af0488..ed53dac2c23 100644 --- a/src/vs/editor/browser/observableUtilities.ts +++ b/src/vs/editor/browser/observableUtilities.ts @@ -18,11 +18,11 @@ import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; /** * Returns a facade for the code editor that provides observables for various states/events. */ -export function obsCodeEditor(editor: ICodeEditor): ObservableCodeEditor { +export function observableCodeEditor(editor: ICodeEditor): ObservableCodeEditor { return ObservableCodeEditor.get(editor); } -class ObservableCodeEditor extends Disposable { +export class ObservableCodeEditor extends Disposable { private static _map = new Map(); /** diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index db99842d621..0169c380f04 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IReader, autorunHandleChanges, derived, derivedOpts, observableFromEvent } from 'vs/base/common/observable'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { obsCodeEditor } from 'vs/editor/browser/observableUtilities'; +import { observableCodeEditor } from 'vs/editor/browser/observableUtilities'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature'; @@ -30,15 +30,15 @@ export class DiffEditorEditors extends Disposable { public readonly modifiedScrollTop = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this.modified.getScrollTop()); public readonly modifiedScrollHeight = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight()); - public readonly modifiedModel = obsCodeEditor(this.modified).model; + public readonly modifiedModel = observableCodeEditor(this.modified).model; public readonly modifiedSelections = observableFromEvent(this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []); public readonly modifiedCursor = derivedOpts({ owner: this, equalsFn: Position.equals }, reader => this.modifiedSelections.read(reader)[0]?.getPosition() ?? new Position(1, 1)); public readonly originalCursor = observableFromEvent(this.original.onDidChangeCursorPosition, () => this.original.getPosition() ?? new Position(1, 1)); - public readonly isOriginalFocused = obsCodeEditor(this.original).isFocused; - public readonly isModifiedFocused = obsCodeEditor(this.modified).isFocused; + public readonly isOriginalFocused = observableCodeEditor(this.original).isFocused; + public readonly isModifiedFocused = observableCodeEditor(this.modified).isFocused; public readonly isFocused = derived(this, reader => this.isOriginalFocused.read(reader) || this.isModifiedFocused.read(reader)); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 021849ea7da..4fdd739ab40 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -15,7 +15,7 @@ import { derivedObservableWithCache, mapObservableArrayCached } from 'vs/base/co import { isUndefined } from 'vs/base/common/types'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { obsCodeEditor, reactToChange, reactToChangeWithStore } from 'vs/editor/browser/observableUtilities'; +import { observableCodeEditor, reactToChange, reactToChangeWithStore } from 'vs/editor/browser/observableUtilities'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -44,7 +44,7 @@ export class InlineCompletionsController extends Disposable { return editor.getContribution(InlineCompletionsController.ID); } - private readonly _editorObs = obsCodeEditor(this.editor); + private readonly _editorObs = observableCodeEditor(this.editor); private readonly _positions = derived(this, reader => this._editorObs.positions.read(reader) ?? [new Position(1, 1)]); private readonly _suggestWidgetAdaptor = this._register(new SuggestWidgetAdaptor( diff --git a/src/vs/editor/contrib/placeholderText/browser/placeholderTextContribution.ts b/src/vs/editor/contrib/placeholderText/browser/placeholderTextContribution.ts index 52c0edd1cb8..f8370994da9 100644 --- a/src/vs/editor/contrib/placeholderText/browser/placeholderTextContribution.ts +++ b/src/vs/editor/contrib/placeholderText/browser/placeholderTextContribution.ts @@ -9,7 +9,7 @@ import { derived, derivedOpts, observableValue } from 'vs/base/common/observable import 'vs/css!./placeholderText'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { obsCodeEditor } from 'vs/editor/browser/observableUtilities'; +import { observableCodeEditor } from 'vs/editor/browser/observableUtilities'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDeltaDecoration, InjectedTextCursorStops } from 'vs/editor/common/model'; @@ -20,7 +20,7 @@ export class PlaceholderTextContribution extends Disposable implements IEditorCo } public static readonly ID = 'editor.contrib.placeholderText'; - private readonly _editorObs = obsCodeEditor(this._editor); + private readonly _editorObs = observableCodeEditor(this._editor); private readonly _placeholderText = observableValue(this, undefined); diff --git a/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts b/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts index dab175973fd..313c561788c 100644 --- a/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts +++ b/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts @@ -8,7 +8,7 @@ import { DisposableStore } from "vs/base/common/lifecycle"; import { IObservable, derivedHandleChanges } from "vs/base/common/observable"; import { ensureNoDisposablesAreLeakedInTestSuite } from "vs/base/test/common/utils"; import { ICodeEditor } from "vs/editor/browser/editorBrowser"; -import { obsCodeEditor } from "vs/editor/browser/observableUtilities"; +import { ObservableCodeEditor, observableCodeEditor } from "vs/editor/browser/observableUtilities"; import { Position } from "vs/editor/common/core/position"; import { Range } from "vs/editor/common/core/range"; import { ViewModel } from "vs/editor/common/viewModel/viewModelImpl"; @@ -18,12 +18,7 @@ suite("CodeEditorWidget", () => { ensureNoDisposablesAreLeakedInTestSuite(); function withTestFixture( - cb: (args: { - editor: ICodeEditor; - viewModel: ViewModel; - log: Log; - derived: IObservable; - }) => void + cb: (args: { editor: ICodeEditor; viewModel: ViewModel; log: Log; derived: IObservable; }) => void ) { withEditorSetupTestFixture(undefined, cb); } @@ -32,80 +27,37 @@ suite("CodeEditorWidget", () => { preSetupCallback: | ((editor: ICodeEditor, disposables: DisposableStore) => void) | undefined, - cb: (args: { - editor: ICodeEditor; - viewModel: ViewModel; - log: Log; - derived: IObservable; - }) => void + cb: (args: { editor: ICodeEditor; viewModel: ViewModel; log: Log; derived: IObservable; }) => void ) { withTestCodeEditor("hello world", {}, (editor, viewModel) => { const disposables = new DisposableStore(); - preSetupCallback?.(editor, disposables); - - const obsEditor = obsCodeEditor(editor); - + const obsEditor = observableCodeEditor(editor); const log = new Log(); - function observableName(obs: IObservable): string { - switch (obs) { - case obsEditor.selections: - return "editor.selections"; - case obsEditor.versionId: - return "editor.versionId"; - case obsEditor.onDidType: - return "editor.onDidType"; - default: - return "unknown"; - } - } - const derived = derivedHandleChanges( { createEmptyChangeSummary: () => undefined, - handleChange: (context, changeSummary) => { - const formattedChange = JSON.stringify( - context.change, - (key, value) => { - if (value instanceof Range) { - return value.toString(); - } - if ( - value === false || - (Array.isArray(value) && value.length === 0) - ) { - return undefined; - } - return value; - } - ); - log.log( - `handle change ${observableName( - context.changedObservable - )} ${formattedChange}` - ); + handleChange: (context) => { + const obsName = observableName(context.changedObservable, obsEditor); + log.log(`handle change: ${obsName} ${formatChange(context.change)}`); return true; }, }, (reader) => { const versionId = obsEditor.versionId.read(reader); - const selection = obsEditor.selections - .read(reader) - ?.map((s) => s.toString()) - .join(", "); + const selection = obsEditor.selections.read(reader)?.map((s) => s.toString()).join(", "); obsEditor.onDidType.read(reader); - const str = `running derived -> selection: ${selection}, value: ${versionId}`; + const str = `running derived: selection: ${selection}, value: ${versionId}`; log.log(str); return str; } ); derived.recomputeInitiallyAndOnChange(disposables); - assert.deepStrictEqual(log.getAndClearEntries(), [ - "running derived -> selection: [1,1 -> 1,1], value: 1", + "running derived: selection: [1,1 -> 1,1], value: 1", ]); cb({ editor, viewModel, log, derived }); @@ -119,8 +71,8 @@ suite("CodeEditorWidget", () => { editor.setPosition(new Position(1, 2)); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'handle change editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":1,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"api","reason":0}', - "running derived -> selection: [1,2 -> 1,2], value: 1", + 'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":1,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"api","reason":0}', + "running derived: selection: [1,2 -> 1,2], value: 1", ]); })); @@ -129,12 +81,12 @@ suite("CodeEditorWidget", () => { editor.trigger("keyboard", "type", { text: "abc" }); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'handle change editor.onDidType "abc"', - 'handle change editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', - 'handle change editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3}', - 'handle change editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4}', - 'handle change editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', - "running derived -> selection: [1,4 -> 1,4], value: 4", + 'handle change: editor.onDidType "abc"', + 'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', + 'handle change: editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3}', + 'handle change: editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4}', + 'handle change: editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', + "running derived: selection: [1,4 -> 1,4], value: 4", ]); })); @@ -143,34 +95,29 @@ suite("CodeEditorWidget", () => { editor.trigger("keyboard", "type", { text: "abc" }); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'handle change editor.onDidType "abc"', - 'handle change editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', - 'handle change editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3}', - 'handle change editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4}', - 'handle change editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', - "running derived -> selection: [1,4 -> 1,4], value: 4", + 'handle change: editor.onDidType "abc"', + 'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', + 'handle change: editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3}', + 'handle change: editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4}', + 'handle change: editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', + "running derived: selection: [1,4 -> 1,4], value: 4", ]); editor.setPosition(new Position(1, 5), "test"); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'handle change editor.selections {"selection":"[1,5 -> 1,5]","modelVersionId":4,"oldSelections":["[1,4 -> 1,4]"],"oldModelVersionId":4,"source":"test","reason":0}', - "running derived -> selection: [1,5 -> 1,5], value: 4", + 'handle change: editor.selections {"selection":"[1,5 -> 1,5]","modelVersionId":4,"oldSelections":["[1,4 -> 1,4]"],"oldModelVersionId":4,"source":"test","reason":0}', + "running derived: selection: [1,5 -> 1,5], value: 4", ]); })); - test("listener interaction", () => { + test("listener interaction (unforced)", () => { let derived: IObservable; let log: Log; - let force = false; withEditorSetupTestFixture( (editor, disposables) => { disposables.add( editor.onDidChangeModelContent(() => { - if (force) { - log.log(">>> before forceUpdate"); - obsCodeEditor(editor).forceUpdate(); - } log.log(">>> before get"); derived.get(); log.log("<<< after get"); @@ -186,53 +133,48 @@ suite("CodeEditorWidget", () => { assert.deepStrictEqual(log.getAndClearEntries(), [ ">>> before get", "<<< after get", - 'handle change editor.onDidType "a"', - 'handle change editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', - 'handle change editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', - "running derived -> selection: [1,2 -> 1,2], value: 2", + 'handle change: editor.onDidType "a"', + 'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', + 'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', + "running derived: selection: [1,2 -> 1,2], value: 2", ]); + } + ); + }); - editor.executeEdits(undefined, [ - { range: new Range(1, 1, 1, 1), text: "x" }, - ]); + test("listener interaction ()", () => { + let derived: IObservable; + let log: Log; + withEditorSetupTestFixture( + (editor, disposables) => { + disposables.add( + editor.onDidChangeModelContent(() => { + log.log(">>> before forceUpdate"); + observableCodeEditor(editor).forceUpdate(); - assert.deepStrictEqual(log.getAndClearEntries(), [ - ">>> before get", - "<<< after get", - 'handle change editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"x","rangeOffset":0}],"eol":"\\n","versionId":3}', - 'handle change editor.selections {"selection":"[1,3 -> 1,3]","modelVersionId":3,"oldSelections":["[1,2 -> 1,2]"],"oldModelVersionId":3,"source":"modelChange","reason":2}', - "running derived -> selection: [1,3 -> 1,3], value: 3", - ]); - - force = true; + log.log(">>> before get"); + derived.get(); + log.log("<<< after get"); + }) + ); + }, + (args) => { + const editor = args.editor; + derived = args.derived; + log = args.log; editor.trigger("keyboard", "type", { text: "a" }); assert.deepStrictEqual(log.getAndClearEntries(), [ ">>> before forceUpdate", ">>> before get", - "handle change editor.versionId undefined", - "running derived -> selection: [1,4 -> 1,4], value: 4", + "handle change: editor.versionId undefined", + "running derived: selection: [1,2 -> 1,2], value: 2", "<<< after get", - 'handle change editor.onDidType "a"', - 'handle change editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"a","rangeOffset":2}],"eol":"\\n","versionId":4}', - 'handle change editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,3 -> 1,3]"],"oldModelVersionId":3,"source":"keyboard","reason":0}', - "running derived -> selection: [1,4 -> 1,4], value: 4", - ]); - - editor.executeEdits(undefined, [ - { range: new Range(1, 1, 1, 1), text: "x" }, - ]); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - ">>> before forceUpdate", - ">>> before get", - "handle change editor.versionId undefined", - "running derived -> selection: [1,5 -> 1,5], value: 5", - "<<< after get", - 'handle change editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"x","rangeOffset":0}],"eol":"\\n","versionId":5}', - 'handle change editor.selections {"selection":"[1,5 -> 1,5]","modelVersionId":5,"oldSelections":["[1,4 -> 1,4]"],"oldModelVersionId":5,"source":"modelChange","reason":2}', - "running derived -> selection: [1,5 -> 1,5], value: 5", + 'handle change: editor.onDidType "a"', + 'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', + 'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', + "running derived: selection: [1,2 -> 1,2], value: 2", ]); } ); @@ -251,3 +193,34 @@ class Log { return entries; } } + +function formatChange(change: unknown) { + return JSON.stringify( + change, + (key, value) => { + if (value instanceof Range) { + return value.toString(); + } + if ( + value === false || + (Array.isArray(value) && value.length === 0) + ) { + return undefined; + } + return value; + } + ); +} + +function observableName(obs: IObservable, obsEditor: ObservableCodeEditor): string { + switch (obs) { + case obsEditor.selections: + return "editor.selections"; + case obsEditor.versionId: + return "editor.versionId"; + case obsEditor.onDidType: + return "editor.onDidType"; + default: + return "unknown"; + } +}