src/vs/workbench/services/textfile/common/encoding.ts/createDecoder
export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
	const minBytesRequiredForDetection = options.minBytesRequiredForDetection ?? options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES;

	return new Promise<IDecodeStreamResult>((resolve, reject) => {
		const target = newWriteableStream<string>(strings => strings.join(''));

		const bufferedChunks: VSBuffer[] = [];
		let bytesBuffered = 0;

		let decoder: IDecoderStream | undefined = undefined;

		const createDecoder = async () => {
			try {

				// detect encoding from buffer
				const detected = await detectEncodingFromBuffer({
					buffer: VSBuffer.concat(bufferedChunks),
					bytesRead: bytesBuffered
				}, options.guessEncoding);

				// ensure to respect overwrite of encoding
				detected.encoding = await options.overwriteEncoding(detected.encoding);

				// decode and write buffered content
				decoder = await DecoderStream.create(detected.encoding);
				const decoded = decoder.write(VSBuffer.concat(bufferedChunks).buffer);
				target.write(decoded);

				bufferedChunks.length = 0;
				bytesBuffered = 0;

				// signal to the outside our detected encoding and final decoder stream
				resolve({
					stream: target,
					detected
				});
			} catch (error) {
				reject(error);
			}
		};

		listenStream(source, {
			onData: async chunk => {

				// if the decoder is ready, we just write directly
				if (decoder) {
					target.write(decoder.write(chunk.buffer));
				}

				// otherwise we need to buffer the data until the stream is ready
				else {
					bufferedChunks.push(chunk);
					bytesBuffered += chunk.byteLength;

					// buffered enough data for encoding detection, create stream
					if (bytesBuffered >= minBytesRequiredForDetection) {

						// pause stream here until the decoder is ready
						source.pause();

						await createDecoder();

						// resume stream now that decoder is ready but
						// outside of this stack to reduce recursion
						setTimeout(() => source.resume());
					}
				}
			},
			onError: error => target.error(error), // simply forward to target
			onEnd: async () => {

				// we were still waiting for data to do the encoding
				// detection. thus, wrap up starting the stream even
				// without all the data to get things going
				if (!decoder) {
					await createDecoder();
				}

				// end the target with the remainders of the decoder
				target.end(decoder?.end());
			}
		});
	});
}