extensions/html-language-features/client/src/customData.ts/getCustomDataSource
export function getCustomDataSource(runtime: Runtime, toDispose: Disposable[]) {
	let localExtensionUris = new Set<string>();
	let externalExtensionUris = new Set<string>();
	const workspaceUris = new Set<string>();

	collectInWorkspaces(workspaceUris);
	collectInExtensions(localExtensionUris, externalExtensionUris);

	const onChange = new EventEmitter<void>();

	toDispose.push(extensions.onDidChange(_ => {
		const newLocalExtensionUris = new Set<string>();
		const newExternalExtensionUris = new Set<string>();
		collectInExtensions(newLocalExtensionUris, newExternalExtensionUris);
		if (hasChanges(newLocalExtensionUris, localExtensionUris) || hasChanges(newExternalExtensionUris, externalExtensionUris)) {
			localExtensionUris = newLocalExtensionUris;
			externalExtensionUris = newExternalExtensionUris;
			onChange.fire();
		}
	}));
	toDispose.push(workspace.onDidChangeConfiguration(e => {
		if (e.affectsConfiguration('html.customData')) {
			workspaceUris.clear();
			collectInWorkspaces(workspaceUris);
			onChange.fire();
		}
	}));

	toDispose.push(workspace.onDidChangeTextDocument(e => {
		const path = e.document.uri.toString();
		if (externalExtensionUris.has(path) || workspaceUris.has(path)) {
			onChange.fire();
		}
	}));

	return {
		get uris() {
			return [...localExtensionUris].concat([...externalExtensionUris], [...workspaceUris]);
		},
		get onDidChange() {
			return onChange.event;
		},
		getContent(uriString: string): Thenable<string> {
			const uri = Uri.parse(uriString);
			if (localExtensionUris.has(uriString)) {
				return workspace.fs.readFile(uri).then(buffer => {
					return new runtime.TextDecoder().decode(buffer);
				});
			}
			return workspace.openTextDocument(uri).then(doc => {
				return doc.getText();
			});
		}
	};
}