src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts/TypeAheadAddon/_onUserData
class TypeAheadAddon:	private _onUserData(data: string): void {
		if (this._timeline?.terminal.buffer.active.type !== 'normal') {
			return;
		}

		// console.log('user data:', JSON.stringify(data));

		const terminal = this._timeline.terminal;
		const buffer = terminal.buffer.active;

		// Detect programs like git log/less that use the normal buffer but don't
		// take input by deafult (fixes #109541)
		if (buffer.cursorX === 1 && buffer.cursorY === terminal.rows - 1) {
			if (buffer.getLine(buffer.cursorY + buffer.baseY)?.getCell(0)?.getChars() === ':') {
				return;
			}
		}

		// the following code guards the terminal prompt to avoid being able to
		// arrow or backspace-into the prompt. Record the lowest X value at which
		// the user gave input, and mark all additions before that as tentative.
		const actualY = buffer.baseY + buffer.cursorY;
		if (actualY !== this._lastRow?.y) {
			this._lastRow = { y: actualY, startingX: buffer.cursorX, endingX: buffer.cursorX, charState: CharPredictState.Unknown };
		} else {
			this._lastRow.startingX = Math.min(this._lastRow.startingX, buffer.cursorX);
			this._lastRow.endingX = Math.max(this._lastRow.endingX, this._timeline.physicalCursor(buffer).x);
		}

		const addLeftNavigating = (p: IPrediction) =>
			this._timeline!.tentativeCursor(buffer).x <= this._lastRow!.startingX
				? this._timeline!.addBoundary(buffer, p)
				: this._timeline!.addPrediction(buffer, p);

		const addRightNavigating = (p: IPrediction) =>
			this._timeline!.tentativeCursor(buffer).x >= this._lastRow!.endingX - 1
				? this._timeline!.addBoundary(buffer, p)
				: this._timeline!.addPrediction(buffer, p);

		/** @see https://github.com/xtermjs/xterm.js/blob/1913e9512c048e3cf56bb5f5df51bfff6899c184/src/common/input/Keyboard.ts */
		const reader = new StringReader(data);
		while (reader.remaining > 0) {
			if (reader.eatCharCode(127)) { // backspace
				const previous = this._timeline.peekEnd();
				if (previous && previous instanceof CharacterPrediction) {
					this._timeline.addBoundary();
				}

				// backspace must be able to read the previously-written character in
				// the event that it needs to undo it
				if (this._timeline.isShowingPredictions) {
					flushOutput(this._timeline.terminal);
				}

				if (this._timeline.tentativeCursor(buffer).x <= this._lastRow!.startingX) {
					this._timeline.addBoundary(buffer, new BackspacePrediction(this._timeline.terminal));
				} else {
					// Backspace decrements our ability to go right.
					this._lastRow.endingX--;
					this._timeline!.addPrediction(buffer, new BackspacePrediction(this._timeline.terminal));
				}

				continue;
			}

			if (reader.eatCharCode(32, 126)) { // alphanum
				const char = data[reader.index - 1];
				const prediction = new CharacterPrediction(this._typeaheadStyle!, char);
				if (this._lastRow.charState === CharPredictState.Unknown) {
					this._timeline.addBoundary(buffer, prediction);
					this._lastRow.charState = CharPredictState.HasPendingChar;
				} else {
					this._timeline.addPrediction(buffer, prediction);
				}

				if (this._timeline.tentativeCursor(buffer).x >= terminal.cols) {
					this._timeline.addBoundary(buffer, new LinewrapPrediction());
				}
				continue;
			}

			const cursorMv = reader.eatRe(CSI_MOVE_RE);
			if (cursorMv) {
				const direction = cursorMv[3] as CursorMoveDirection;
				const p = new CursorMovePrediction(direction, !!cursorMv[2], Number(cursorMv[1]) || 1);
				if (direction === CursorMoveDirection.Back) {
					addLeftNavigating(p);
				} else {
					addRightNavigating(p);
				}
				continue;
			}

			if (reader.eatStr(`${ESC}f`)) {
				addRightNavigating(new CursorMovePrediction(CursorMoveDirection.Forwards, true, 1));
				continue;
			}

			if (reader.eatStr(`${ESC}b`)) {
				addLeftNavigating(new CursorMovePrediction(CursorMoveDirection.Back, true, 1));
				continue;
			}

			if (reader.eatChar('\r') && buffer.cursorY < terminal.rows - 1) {
				this._timeline.addPrediction(buffer, new NewlinePrediction());
				continue;
			}

			// something else
			this._timeline.addBoundary(buffer, new HardBoundary());
			break;
		}

		if (this._timeline.length === 1) {
			this._deferClearingPredictions();
			this._typeaheadStyle!.startTracking();
		}
	}