<commit_before>import {
	Logger, logger,
	DebugSession, LoggingDebugSession,
	InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent,
	Thread, StackFrame, Scope, Source, Breakpoint
} from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import { ChildProcess } from 'child_process';
import { basename, normalize, join, isAbsolute } from 'path';
import * as fs from 'fs';
import { validatePath } from './bashRuntime';
import { getWSLPath, reverseWSLPath, escapeCharactersInBashdbArg, getWSLLauncherPath } from './handlePath';
import { EventSource } from './eventSource';
import { spawnBashScript } from './spawnBash';

export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {

	// Non-optional arguments are guaranteed to be defined in extension.ts: resolveDebugConfiguration().
	args: string[];
	env: object;
	cwd: string;
	cwdEffective: string;
	program: string;
	programEffective: string;
	pathBash: string;
	pathBashdb: string;
	pathBashdbLib: string;
	pathCat: string;
	pathMkfifo: string;
	pathPkill: string;
	terminalKind?: 'integrated' | 'external' | 'debugConsole';
	showDebugOutput?: boolean;
	/** enable logging the Debug Adapter Protocol */
	trace?: boolean;
}

export class BashDebugSession extends LoggingDebugSession {

	private static THREAD_ID = 42;
	private static END_MARKER = "############################################################";

	private launchArgs: LaunchRequestArguments;

	private proxyProcess: ChildProcess;

	private currentBreakpointIds = new Map<string, Array<number>>();
	private proxyData = new Map<string, string>();

	private fullDebugOutput = [""];
	private fullDebugOutputIndex = 0;

	private debuggerExecutableBusy = false;
	private debuggerExecutableClosing = false;

	private outputEventSource = new EventSource();

	private debuggerProcessParentId = -1;

	public constructor() {
		super("bash-debug.txt");
		this.setDebuggerLinesStartAt1(true);
		this.setDebuggerColumnsStartAt1(true);
	}

	protected initializeRequest(response: DebugProtocol.InitializeResponse, _args: DebugProtocol.InitializeRequestArguments): void {

		response.body = response.body || {};

		response.body.supportsConditionalBreakpoints = true;
		response.body.supportsConfigurationDoneRequest = false;
		response.body.supportsEvaluateForHovers = true;
		response.body.supportsStepBack = false;
		response.body.supportsSetVariable = false;
		this.sendResponse(response);
	}

	protected disconnectRequest(response: DebugProtocol.DisconnectResponse, _args: DebugProtocol.DisconnectArguments): void {

		this.releaseDebugger();

		spawnBashScript(`${this.launchArgs.pathPkill} -KILL -P "${this.debuggerProcessParentId}"; ${this.launchArgs.pathPkill} -TERM -P "${this.proxyData["PROXYID"]}"`,
			this.launchArgs.pathBash,
			data => this.sendEvent(new OutputEvent(`${data}`, 'console')));

		this.proxyProcess.on("exit", () => {
			this.debuggerExecutableClosing = true;
			this.sendResponse(response);
		});
	}

	protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): Promise<void> {

		this.launchArgs = args;

		logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop, false);

		if (process.platform === "win32") {
			args.cwdEffective = `${getWSLPath(args.cwd)}`;
			args.programEffective = `${getWSLPath(args.program)}`;
		}
		else {
			args.cwdEffective = args.cwd;
			args.programEffective = args.program;
		}

		const errorMessage = validatePath(
			args.cwdEffective, args.pathBash, args.pathBashdb, args.pathCat, args.pathMkfifo, args.pathPkill);

		if (errorMessage !== "") {
			response.success = false;
			response.message = errorMessage;
			this.sendResponse(response);
			return;
		}

		const fifo_path = "/tmp/vscode-bash-debug-fifo-" + (Math.floor(Math.random() * 10000) + 10000);

		// http://tldp.org/LDP/abs/html/io-redirection.html
		this.proxyProcess = spawnBashScript(
			`function cleanup()
		{
			exit_code=$?
			trap '' ERR SIGINT SIGTERM EXIT
			exec 4>&-
			rm "${fifo_path}_in"
			rm "${fifo_path}"
			exit $exit_code
		}
		echo "::PROXYID::$$" >&2
		trap 'cleanup' ERR SIGINT SIGTERM EXIT
		mkfifo "${fifo_path}"
		mkfifo "${fifo_path}_in"

		"${args.pathCat}" "${fifo_path}" &
		exec 4>"${fifo_path}"
		"${args.pathCat}" >"${fifo_path}_in"`
				.replace("\r", ""),
			this.launchArgs.pathBash,
			(data, category) => {
				if (args.showDebugOutput || category === "console") {
					this.sendEvent(new OutputEvent(`${data}`, category));
				}
			});

		this.proxyProcess.stdin.write(`examine Debug environment: bash_ver=$BASH_VERSION, bashdb_ver=$_Dbg_release, program=$0, args=$*\nprint "$PPID"\nhandle INT stop\nprint '${BashDebugSession.END_MARKER}'\n`);

		let envVars = Object.keys(this.launchArgs.env)
			.map(e => `export ${e}='${this.launchArgs.env[e]}';`)
			.reduce((prev, next) => prev + next, ``);

		if (this.launchArgs.terminalKind === "debugConsole") {
			spawnBashScript(
				`${envVars}cd "${args.cwdEffective}"; while [[ ! -p "${fifo_path}" ]]; do sleep 0.25; done
				"${args.pathBash}" "${args.pathBashdb}" --quiet --tty "${fifo_path}" --tty_in "${fifo_path}_in" --library "${args.pathBashdbLib}" -- "${args.programEffective}" ${args.args.map(e => `"` + e.replace(`"`, `\\\"`) + `"`).join(` `)}`
					.replace("\r", "").replace("\n", "; "),
				this.launchArgs.pathBash,
				(data, category) => this.sendEvent(new OutputEvent(`${data}`, category)));
		}
		else {
			const currentShell = (process.platform === "win32") ? getWSLLauncherPath(true) : args.pathBash;
			const optionalBashPathArgument = (currentShell !== args.pathBash) ? args.pathBash : "";
			const termArgs: DebugProtocol.RunInTerminalRequestArguments = {
				kind: this.launchArgs.terminalKind,
				title: "Bash Debug Console",
				cwd: ".",
				args: [currentShell, optionalBashPathArgument, `-c`,
					`${envVars}cd "${args.cwdEffective}"; while [[ ! -p "${fifo_path}" ]]; do sleep 0.25; done
				"${args.pathBash}" "${args.pathBashdb}" --quiet --tty "${fifo_path}" --tty_in "${fifo_path}_in" --library "${args.pathBashdbLib}" -- "${args.programEffective}" ${args.args.map(e => `"` + e.replace(`"`, `\\\"`) + `"`).join(` `)}`
						.replace("\r", "").replace("\n", "; ")
				].filter(arg => arg !== ""),
			};

			this.runInTerminalRequest(termArgs, 10000, (response) => {
				if (!response.success) {
					this.sendEvent(new OutputEvent(`${JSON.stringify(response)}`, 'console'));
				}
			});
		}

		await this.onDebuggerAvailable();

		this.processDebugTerminalOutput();
		this.launchRequestFinalize(response, args);
	}

	private async launchRequestFinalize(response: DebugProtocol.LaunchResponse, _args: LaunchRequestArguments): Promise<void> {

		while (await this.onNextDebuggerOutput()) {
			for (let i = 0; i < this.fullDebugOutput.length; i++) {<commit_msg>fix: Fix indentation (\t) on string template causes commands to fail on bash<commit_after>import {
	Logger, logger,
	DebugSession, LoggingDebugSession,
	InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent,
	Thread, StackFrame, Scope, Source, Breakpoint
} from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import { ChildProcess } from 'child_process';
import { basename, normalize, join, isAbsolute } from 'path';
import * as fs from 'fs';
import { validatePath } from './bashRuntime';
import { getWSLPath, reverseWSLPath, escapeCharactersInBashdbArg, getWSLLauncherPath } from './handlePath';
import { EventSource } from './eventSource';
import { spawnBashScript } from './spawnBash';

export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {

	// Non-optional arguments are guaranteed to be defined in extension.ts: resolveDebugConfiguration().
	args: string[];
	env: object;
	cwd: string;
	cwdEffective: string;
	program: string;
	programEffective: string;
	pathBash: string;
	pathBashdb: string;
	pathBashdbLib: string;
	pathCat: string;
	pathMkfifo: string;
	pathPkill: string;
	terminalKind?: 'integrated' | 'external' | 'debugConsole';
	showDebugOutput?: boolean;
	/** enable logging the Debug Adapter Protocol */
	trace?: boolean;
}

export class BashDebugSession extends LoggingDebugSession {

	private static THREAD_ID = 42;
	private static END_MARKER = "############################################################";

	private launchArgs: LaunchRequestArguments;

	private proxyProcess: ChildProcess;

	private currentBreakpointIds = new Map<string, Array<number>>();
	private proxyData = new Map<string, string>();

	private fullDebugOutput = [""];
	private fullDebugOutputIndex = 0;

	private debuggerExecutableBusy = false;
	private debuggerExecutableClosing = false;

	private outputEventSource = new EventSource();

	private debuggerProcessParentId = -1;

	public constructor() {
		super("bash-debug.txt");
		this.setDebuggerLinesStartAt1(true);
		this.setDebuggerColumnsStartAt1(true);
	}

	protected initializeRequest(response: DebugProtocol.InitializeResponse, _args: DebugProtocol.InitializeRequestArguments): void {

		response.body = response.body || {};

		response.body.supportsConditionalBreakpoints = true;
		response.body.supportsConfigurationDoneRequest = false;
		response.body.supportsEvaluateForHovers = true;
		response.body.supportsStepBack = false;
		response.body.supportsSetVariable = false;
		this.sendResponse(response);
	}

	protected disconnectRequest(response: DebugProtocol.DisconnectResponse, _args: DebugProtocol.DisconnectArguments): void {

		this.releaseDebugger();

		spawnBashScript(`${this.launchArgs.pathPkill} -KILL -P "${this.debuggerProcessParentId}"; ${this.launchArgs.pathPkill} -TERM -P "${this.proxyData["PROXYID"]}"`,
			this.launchArgs.pathBash,
			data => this.sendEvent(new OutputEvent(`${data}`, 'console')));

		this.proxyProcess.on("exit", () => {
			this.debuggerExecutableClosing = true;
			this.sendResponse(response);
		});
	}

	protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): Promise<void> {

		this.launchArgs = args;

		logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop, false);

		if (process.platform === "win32") {
			args.cwdEffective = `${getWSLPath(args.cwd)}`;
			args.programEffective = `${getWSLPath(args.program)}`;
		}
		else {
			args.cwdEffective = args.cwd;
			args.programEffective = args.program;
		}

		const errorMessage = validatePath(
			args.cwdEffective, args.pathBash, args.pathBashdb, args.pathCat, args.pathMkfifo, args.pathPkill);

		if (errorMessage !== "") {
			response.success = false;
			response.message = errorMessage;
			this.sendResponse(response);
			return;
		}

		const fifo_path = "/tmp/vscode-bash-debug-fifo-" + (Math.floor(Math.random() * 10000) + 10000);

		// http://tldp.org/LDP/abs/html/io-redirection.html
		this.proxyProcess = spawnBashScript(
			`function cleanup()
		{
			exit_code=$?
			trap '' ERR SIGINT SIGTERM EXIT
			exec 4>&-
			rm "${fifo_path}_in"
			rm "${fifo_path}"
			exit $exit_code
		}
		echo "::PROXYID::$$" >&2
		trap 'cleanup' ERR SIGINT SIGTERM EXIT
		mkfifo "${fifo_path}"
		mkfifo "${fifo_path}_in"

		"${args.pathCat}" "${fifo_path}" &
		exec 4>"${fifo_path}"
		"${args.pathCat}" >"${fifo_path}_in"`
				.replace("\r", ""),
			this.launchArgs.pathBash,
			(data, category) => {
				if (args.showDebugOutput || category === "console") {
					this.sendEvent(new OutputEvent(`${data}`, category));
				}
			});

		this.proxyProcess.stdin.write(`examine Debug environment: bash_ver=$BASH_VERSION, bashdb_ver=$_Dbg_release, program=$0, args=$*\nprint "$PPID"\nhandle INT stop\nprint '${BashDebugSession.END_MARKER}'\n`);

		let envVars = Object.keys(this.launchArgs.env)
			.map(e => `export ${e}='${this.launchArgs.env[e]}';`)
			.reduce((prev, next) => prev + next, ``);

		const command = this.joinCommands(
			`${envVars}cd "${args.cwdEffective}"`,
			`while [[ ! -p "${fifo_path}" ]]; do sleep 0.25; done`,
			`"${args.pathBash}" "${args.pathBashdb}" --quiet --tty "${fifo_path}" --tty_in "${fifo_path}_in" --library "${args.pathBashdbLib}" -- "${args.programEffective}" ${args.args.map(e => `"` + e.replace(`"`, `\\\"`) + `"`).join(` `)}`)

		if (this.launchArgs.terminalKind === "debugConsole") {
			spawnBashScript(
				command,
				this.launchArgs.pathBash,
				(data, category) => this.sendEvent(new OutputEvent(`${data}`, category)));
		}
		else {
			const currentShell = (process.platform === "win32") ? getWSLLauncherPath(true) : args.pathBash;
			const optionalBashPathArgument = (currentShell !== args.pathBash) ? args.pathBash : "";
			const termArgs: DebugProtocol.RunInTerminalRequestArguments = {
				kind: this.launchArgs.terminalKind,
				title: "Bash Debug Console",
				cwd: ".",
				args: [currentShell, optionalBashPathArgument, `-c`, command].filter(arg => arg !== ""),
			};

			this.runInTerminalRequest(termArgs, 10000, (response) => {
				if (!response.success) {
					this.sendEvent(new OutputEvent(`${JSON.stringify(response)}`, 'console'));
				}
			});
		}

		await this.onDebuggerAvailable();

		this.processDebugTerminalOutput();
		this.launchRequestFinalize(response, args);
	}

	private joinCommands(...cmd: string[]): string {
		return cmd.join(`; `);
	}

	private async launchRequestFinalize(response: DebugProtocol.LaunchResponse, _args: LaunchRequestArguments): Promise<void> {

		while (await this.onNextDebuggerOutput()) {
			for (let i = 0; i < this.fullDebugOutput.length; i++) {
{'fraction_non_alphanumeric': 0.16329264662101817, 'fraction_numerical': 0.005135456981244418, 'mean_word_length': 4.945575221238938, 'pattern_counts': {'":': 2, '<': 15, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3169498', 'n_tokens_mistral': 4800, 'n_tokens_neox': 4306, 'n_words': 868}