-
Notifications
You must be signed in to change notification settings - Fork 36.4k
DomWidget / ChatStatus cleanup #278860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
DomWidget / ChatStatus cleanup #278860
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -99,18 +99,43 @@ function injectBuiltinExtensionsPlugin(): Plugin { | |||||
| function createHotClassSupport(): Plugin { | ||||||
| return { | ||||||
| name: 'createHotClassSupport', | ||||||
| transform(code, id) { | ||||||
| if (id.endsWith('.ts')) { | ||||||
| if (code.includes('createHotClass')) { | ||||||
| code = code + `\n | ||||||
| transform: { | ||||||
| order: 'pre', | ||||||
| handler: (code, id) => { | ||||||
| if (id.endsWith('.ts')) { | ||||||
| let needsHMRAccept = false; | ||||||
| const hasCreateHotClass = code.includes('createHotClass'); | ||||||
| const hasDomWidget = code.includes('DomWidget'); | ||||||
|
|
||||||
| if (!hasCreateHotClass && !hasDomWidget) { | ||||||
| return undefined; | ||||||
| } | ||||||
|
|
||||||
| if (hasCreateHotClass) { | ||||||
| needsHMRAccept = true; | ||||||
| } | ||||||
|
|
||||||
| if (hasDomWidget) { | ||||||
| const matches = code.matchAll(/class\s+([a-zA-Z0-9_]+)\s+extends\s+DomWidget/g); | ||||||
| /// @ts-ignore | ||||||
|
||||||
| /// @ts-ignore | |
| // @ts-expect-error |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| /*--------------------------------------------------------------------------------------------- | ||
| * Copyright (c) Microsoft Corporation. All rights reserved. | ||
| * Licensed under the MIT License. See License.txt in the project root for license information. | ||
| *--------------------------------------------------------------------------------------------*/ | ||
|
|
||
| import { isHotReloadEnabled } from '../../../base/common/hotReload.js'; | ||
| import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; | ||
| import { ISettableObservable, IObservable, autorun, constObservable, derived, observableValue } from '../../../base/common/observable.js'; | ||
| import { IInstantiationService, GetLeadingNonServiceArgs } from '../../instantiation/common/instantiation.js'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find |
||
|
|
||
| /** | ||
| * The DomWidget class provides a standard to define reusable UI components. | ||
| * It is disposable and defines a single root element of type HTMLElement. | ||
| * It also provides static helper methods to create and append widgets to the DOM, | ||
| * with support for hot module replacement during development. | ||
| */ | ||
| export abstract class DomWidget extends Disposable { | ||
| /** | ||
| * Appends the widget to the provided DOM element. | ||
| */ | ||
| public static createAppend<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, dom: HTMLElement, store: DisposableStore, ...params: TArgs): void { | ||
| if (!isHotReloadEnabled()) { | ||
| const widget = new this(...params); | ||
| dom.appendChild(widget.element); | ||
| store.add(widget); | ||
| return; | ||
| } | ||
|
|
||
| const observable = this.createObservable(store, ...params); | ||
| store.add(autorun((reader) => { | ||
| const widget = observable.read(reader); | ||
| dom.appendChild(widget.element); | ||
| reader.store.add(toDisposable(() => widget.element.remove())); | ||
| reader.store.add(widget); | ||
| })); | ||
| } | ||
|
|
||
| /** | ||
| * Creates the widget in a new div element with "display: contents". | ||
| */ | ||
| public static createInContents<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, store: DisposableStore, ...params: TArgs): HTMLDivElement { | ||
| const div = document.createElement('div'); | ||
| div.style.display = 'contents'; | ||
| this.createAppend(div, store, ...params); | ||
| return div; | ||
| } | ||
|
|
||
| /** | ||
| * Creates an observable instance of the widget. | ||
| * The observable will change when hot module replacement occurs. | ||
| */ | ||
| public static createObservable<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, store: DisposableStore, ...params: TArgs): IObservable<T> { | ||
| if (!isHotReloadEnabled()) { | ||
| return constObservable(new this(...params)); | ||
| } | ||
|
|
||
| const id = (this as unknown as HotReloadable)[_hotReloadId]; | ||
| const observable = id ? hotReloadedWidgets.get(id) : undefined; | ||
|
|
||
| if (!observable) { | ||
| return constObservable(new this(...params)); | ||
| } | ||
|
|
||
| return derived(reader => { | ||
| const Ctor = observable.read(reader); | ||
| return new Ctor(...params) as T; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Appends the widget to the provided DOM element. | ||
| */ | ||
| public static instantiateAppend<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, dom: HTMLElement, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): void { | ||
| if (!isHotReloadEnabled()) { | ||
| const widget = instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params); | ||
| dom.appendChild(widget.element); | ||
| store.add(widget); | ||
| return; | ||
| } | ||
|
|
||
| const observable = this.instantiateObservable(instantiationService, store, ...params); | ||
| let lastWidget: DomWidget | undefined = undefined; | ||
| store.add(autorun((reader) => { | ||
| const widget = observable.read(reader); | ||
| if (lastWidget) { | ||
| lastWidget.element.replaceWith(widget.element); | ||
| } else { | ||
| dom.appendChild(widget.element); | ||
| } | ||
| lastWidget = widget; | ||
|
|
||
| reader.delayedStore.add(widget); | ||
| })); | ||
| } | ||
|
|
||
| /** | ||
| * Creates the widget in a new div element with "display: contents". | ||
| * If possible, prefer `instantiateAppend`, as it avoids an extra div in the DOM. | ||
| */ | ||
| public static instantiateInContents<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): HTMLDivElement { | ||
| const div = document.createElement('div'); | ||
| div.style.display = 'contents'; | ||
| this.instantiateAppend(instantiationService, div, store, ...params); | ||
| return div; | ||
| } | ||
|
|
||
| /** | ||
| * Creates an observable instance of the widget. | ||
| * The observable will change when hot module replacement occurs. | ||
| */ | ||
| public static instantiateObservable<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): IObservable<T> { | ||
| if (!isHotReloadEnabled()) { | ||
| return constObservable(instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params)); | ||
| } | ||
|
|
||
| const id = (this as unknown as HotReloadable)[_hotReloadId]; | ||
| const observable = id ? hotReloadedWidgets.get(id) : undefined; | ||
|
|
||
| if (!observable) { | ||
| return constObservable(instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params)); | ||
| } | ||
|
|
||
| return derived(reader => { | ||
| const Ctor = observable.read(reader); | ||
| return instantiationService.createInstance(Ctor, ...params) as T; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * @deprecated Do not call manually! Only for use by the hot reload system (a vite plugin will inject calls to this method in dev mode). | ||
| */ | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| public static registerWidgetHotReplacement(this: new (...args: any[]) => DomWidget, id: string): void { | ||
| if (!isHotReloadEnabled()) { | ||
| return; | ||
| } | ||
| let observable = hotReloadedWidgets.get(id); | ||
| if (!observable) { | ||
| observable = observableValue(id, this); | ||
| hotReloadedWidgets.set(id, observable); | ||
| } else { | ||
| observable.set(this, undefined); | ||
| } | ||
| (this as unknown as HotReloadable)[_hotReloadId] = id; | ||
| } | ||
|
|
||
| /** Always returns the same element. */ | ||
| abstract get element(): HTMLElement; | ||
| } | ||
|
|
||
| const _hotReloadId = Symbol('DomWidgetHotReloadId'); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const hotReloadedWidgets = new Map<string, ISettableObservable<new (...args: any[]) => DomWidget>>(); | ||
|
|
||
| interface HotReloadable { | ||
| [_hotReloadId]?: string; | ||
| } | ||
|
|
||
| type DomWidgetCtor<TArgs extends unknown[], T extends DomWidget> = { | ||
| new(...args: TArgs): T; | ||
|
|
||
| createObservable(store: DisposableStore, ...params: TArgs): IObservable<T>; | ||
| instantiateObservable(instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): IObservable<T>; | ||
| createAppend(dom: HTMLElement, store: DisposableStore, ...params: TArgs): void; | ||
| instantiateAppend(instantiationService: IInstantiationService, dom: HTMLElement, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): void; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern
/class\s+([a-zA-Z0-9_]+)\s+extends\s+DomWidget/gmay not correctly handle all cases. It won't match classes that have TypeScript generic parameters (e.g.,class MyWidget<T> extends DomWidget) or those with multiple spaces/line breaks. Consider using a more robust pattern or AST-based parsing for reliability.