Improves audio cue implementation.

This commit is contained in:
Henning Dieterichs 2022-02-18 16:54:37 +01:00
parent 1565485f7c
commit 3bb027eb95
No known key found for this signature in database
GPG key ID: 771381EFFDB9EC06
2 changed files with 62 additions and 50 deletions

View file

@ -12,7 +12,7 @@ import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/edito
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { autorun, autorunDelta, constObservable, debouncedObservable, fromEvent, fromPromise, IObservable, LazyDerived, wasEventTriggeredRecently } from 'vs/workbench/contrib/audioCues/browser/observable';
import { autorun, autorunDelta, constObservable, debouncedObservable, derivedObservable, fromEvent, fromPromise, IObservable, LazyDerived, wasEventTriggeredRecently } from 'vs/workbench/contrib/audioCues/browser/observable';
import { ITextModel } from 'vs/editor/common/model';
import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextController';
import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService';
@ -83,14 +83,14 @@ export class AudioCueLineFeatureContribution
editorModel: ITextModel,
store: DisposableStore
): void {
const observableFeatureStates = this.features.map((feature) =>
feature.getObservableState(editor, editorModel)
);
const curLineNumber = fromEvent(
editor.onDidChangeCursorPosition,
(args) => {
if (args && args.reason !== CursorChangeReason.Explicit) {
if (
args &&
args.reason !== CursorChangeReason.Explicit &&
args.reason !== CursorChangeReason.NotSet
) {
// Ignore cursor changes caused by navigation (e.g. which happens when execution is paused).
return undefined;
}
@ -99,61 +99,55 @@ export class AudioCueLineFeatureContribution
);
const debouncedLineNumber = debouncedObservable(curLineNumber, 100, store);
const lineNumberWithObservableFeatures = debouncedLineNumber.map(
(lineNumber) =>
lineNumber === undefined
? undefined
: {
lineNumber,
featureStatesForLine: observableFeatureStates.map(
(featureResult, idx) =>
// This caches the feature state for the active line
new LazyDerived(
(reader) =>
this.audioCueService
.isEnabled(this.features[idx].audioCue)
.read(reader) &&
featureResult.read(reader).isPresent(lineNumber),
'isActiveForLine'
)
),
const isFeaturePresentInDebouncedLine = (
feature: LineFeature,
lineFeatureState: IObservable<LineFeatureState>
): IObservable<boolean> =>
derivedObservable(
`isPresentInLine:${feature.audioCue.name}`,
(reader) => {
if (!this.audioCueService.isEnabled(feature.audioCue).read(reader)) {
return false;
}
);
const lineNumber = debouncedLineNumber.read(reader);
return lineNumber === undefined
? false
: lineFeatureState.read(reader).isPresent(lineNumber);
}
);
const isTyping = wasEventTriggeredRecently(
editorModel.onDidChangeContent.bind(editorModel),
1000,
store
);
const featureStatesBeforeTyping = isTyping.map(
(isTyping) =>
(!isTyping
? undefined
: lineNumberWithObservableFeatures
.get()
?.featureStatesForLine?.map((featureState, idx) =>
this.features[idx].debounceWhileTyping
? featureState.get()
: undefined
)) ?? []
);
const state = new LazyDerived((reader) => {
const lineInfo = lineNumberWithObservableFeatures.read(reader);
if (lineInfo === undefined) {
return undefined;
}
return {
lineNumber: lineInfo.lineNumber,
const featureStates = this.features.map((feature) => {
const isFeaturePresent = isFeaturePresentInDebouncedLine(
feature,
feature.getObservableState(editor, editorModel)
);
return derivedObservable(
`typingDebouncedFeatureState:\n${feature.audioCue.name}`,
(reader) =>
feature.debounceWhileTyping && isTyping.read(reader)
? (debouncedLineNumber.read(reader), isFeaturePresent.get())
: isFeaturePresent.read(reader)
);
});
const state = new LazyDerived(
(reader) => ({
lineNumber: debouncedLineNumber.read(reader),
featureStates: new Map(
lineInfo.featureStatesForLine.map((featureState, idx) => [
this.features[idx],
featureStatesBeforeTyping.read(reader)[idx] ??
featureState.read(reader),
this.features.map((feature, idx) => [
feature,
featureStates[idx].read(reader),
])
),
};
}, 'state');
}),
'state'
);
store.add(
autorunDelta(state, ({ lastValue, newValue }) => {

View file

@ -314,6 +314,9 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
}
}
export namespace autorun {
export const Observer = AutorunObserver;
}
export function autorunDelta<T>(
observable: IObservable<T>,
handler: (args: { lastValue: T | undefined; newValue: T }) => void
@ -330,6 +333,9 @@ export function autorunDelta<T>(
// == Lazy Derived ==
export function derivedObservable<T>(name: string, computeFn: (reader: IReader) => T): IObservable<T> {
return new LazyDerived(computeFn, name);
}
export class LazyDerived<T> extends ConvenientObservable<T> {
private readonly observer: LazyDerivedObserver<T>;
@ -346,6 +352,10 @@ export class LazyDerived<T> extends ConvenientObservable<T> {
this.observer.unsubscribe(observer);
}
public override read(reader: IReader): T {
return this.observer.read(reader);
}
public get(): T {
return this.observer.get();
}
@ -485,6 +495,10 @@ class LazyDerivedObserver<T>
}
}
export namespace LazyDerived {
export const Observer = LazyDerivedObserver;
}
export function fromPromise<T>(promise: Promise<T>): IObservable<{ value?: T }> {
const observable = new ObservableValue<{ value?: T }>({}, 'promiseValue');
promise.then((value) => {
@ -553,6 +567,10 @@ class FromEventObservable<TArgs, T> extends BaseObservable<T> {
}
}
export namespace fromEvent {
export const Observer = FromEventObservable;
}
export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: number, disposableStore: DisposableStore): IObservable<T> {
const debouncedObservable = new ObservableValue(observable.get(), 'debounced');