<commit_before>import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as vs from "vscode";
import { window, workspace } from "vscode";
import { CHROME_OS_DEVTOOLS_PORT, isChromeOS, pubPath, reactivateDevToolsAction, skipAction } from "../../shared/constants";
import { LogCategory, VmService } from "../../shared/enums";
import { DartWorkspaceContext, Logger } from "../../shared/interfaces";
import { CategoryLogger } from "../../shared/logging";
import { UnknownNotification } from "../../shared/services/interfaces";
import { StdIOService } from "../../shared/services/stdio_service";
import { usingCustomScript } from "../../shared/utils";
import { getRandomInt } from "../../shared/utils/fs";
import { waitFor } from "../../shared/utils/promises";
import { envUtils, isRunningLocally } from "../../shared/vscode/utils";
import { Analytics } from "../analytics";
import { DebugCommands, debugSessions } from "../commands/debug";
import { config } from "../config";
import { PubGlobal } from "../pub/global";
import { getToolEnv } from "../utils/processes";
import { DartDebugSessionInformation } from "../utils/vscode/debug";

const devtoolsPackageID = "devtools";
const devtoolsPackageName = "Dart DevTools";

// This starts off undefined, which means we'll read from config.devToolsPort and fall back to undefined (use default).
// Once we get a port we'll update this variable so that if we restart (eg. a silent extension restart due to
// SDK change or similar) we will try to use the same port, so if the user has browser windows open they're
// still valid.
let portToBind: number | undefined;

/// Handles launching DevTools in the browser and managing the underlying service.
export class DevToolsManager implements vs.Disposable {
	private readonly disposables: vs.Disposable[] = [];
	private readonly devToolsStatusBarItem = vs.window.createStatusBarItem(vs.StatusBarAlignment.Right, 100);
	private devToolsActivationPromise: Promise<void> | undefined;
	public get devToolsActivation() { return this.devToolsActivationPromise; }

	/// Resolves to the DevTools URL. This is created immediately when a new process is being spawned so that
	/// concurrent launches can wait on the same promise.
	private devtoolsUrl: Thenable<string> | undefined;

	constructor(private readonly logger: Logger, private readonly workspaceContext: DartWorkspaceContext, private readonly debugCommands: DebugCommands, private readonly analytics: Analytics, private readonly pubGlobal: PubGlobal) {
		this.disposables.push(this.devToolsStatusBarItem);

		if (workspaceContext.workspaceConfig?.activateDevToolsEagerly) {
			this.preActivate(true).then(
				() => { this.logger.info(`Finished background activating DevTools`); },
				(e) => {
					this.logger.error("Failed to background activate DevTools");
					this.logger.error(e);
					vs.window.showErrorMessage(`Failed to activate DevTools: ${e}`);
				});
		}
	}

	private async preActivate(silent: boolean): Promise<void> {
		this.devToolsActivationPromise = this.pubGlobal.backgroundActivate(devtoolsPackageName, devtoolsPackageID, silent, this.workspaceContext.workspaceConfig?.devtoolsActivateScript);
		await this.devToolsActivationPromise;
	}

	/// Spawns DevTools and returns the full URL to open for that session
	///   eg. http://127.0.0.1:8123/?port=8543
	public async spawnForSession(session: DartDebugSessionInformation & { vmServiceUri: string }, reuseWindows: boolean, notify: boolean, page: string | undefined): Promise<{ url: string, dispose: () => void } | undefined> {
		this.analytics.logDebuggerOpenDevTools();

		// If we're mid-silent-activation, wait until that's finished.
		await this.devToolsActivationPromise;

		// Don't try to check for install when we run eagerly.
		if (!this.workspaceContext.workspaceConfig?.activateDevToolsEagerly) {
			const isAvailable = await this.pubGlobal.promptToInstallIfRequired(devtoolsPackageName, devtoolsPackageID, undefined, "0.1.10", this.workspaceContext.workspaceConfig?.devtoolsActivateScript, true);
			if (!isAvailable) {
				return undefined;
			}
		}

		if (!this.devtoolsUrl) {
			this.devtoolsUrl = vs.window.withProgress({
				location: vs.ProgressLocation.Notification,
				title: "Starting Dart DevTools...",
			}, async (_) => this.startServer());
		}
		try {
			const url = await this.devtoolsUrl;
			await vs.window.withProgress({
				location: vs.ProgressLocation.Notification,
				title: "Opening Dart DevTools...",
			}, async (_) => {
				const queryParams: { [key: string]: string | undefined } = {
					hide: "debugger",
					ide: "VSCode",
					theme: config.useDevToolsDarkTheme ? "dark" : undefined,
				};
				const canLaunchDevToolsThroughService = isRunningLocally
					&& !process.env.DART_CODE_IS_TEST_RUN
					&& await waitFor(() => this.debugCommands.vmServices.serviceIsRegistered(VmService.LaunchDevTools), 500);
				if (canLaunchDevToolsThroughService) {
					try {
						await session.session.customRequest(
							"service",
							{
								params: {
									notify,
									page,
									queryParams,<commit_msg>Skip version checking when DevTools is already running<commit_after>import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as vs from "vscode";
import { window, workspace } from "vscode";
import { CHROME_OS_DEVTOOLS_PORT, isChromeOS, pubPath, reactivateDevToolsAction, skipAction } from "../../shared/constants";
import { LogCategory, VmService } from "../../shared/enums";
import { DartWorkspaceContext, Logger } from "../../shared/interfaces";
import { CategoryLogger } from "../../shared/logging";
import { UnknownNotification } from "../../shared/services/interfaces";
import { StdIOService } from "../../shared/services/stdio_service";
import { usingCustomScript } from "../../shared/utils";
import { getRandomInt } from "../../shared/utils/fs";
import { waitFor } from "../../shared/utils/promises";
import { envUtils, isRunningLocally } from "../../shared/vscode/utils";
import { Analytics } from "../analytics";
import { DebugCommands, debugSessions } from "../commands/debug";
import { config } from "../config";
import { PubGlobal } from "../pub/global";
import { getToolEnv } from "../utils/processes";
import { DartDebugSessionInformation } from "../utils/vscode/debug";

const devtoolsPackageID = "devtools";
const devtoolsPackageName = "Dart DevTools";

// This starts off undefined, which means we'll read from config.devToolsPort and fall back to undefined (use default).
// Once we get a port we'll update this variable so that if we restart (eg. a silent extension restart due to
// SDK change or similar) we will try to use the same port, so if the user has browser windows open they're
// still valid.
let portToBind: number | undefined;

/// Handles launching DevTools in the browser and managing the underlying service.
export class DevToolsManager implements vs.Disposable {
	private readonly disposables: vs.Disposable[] = [];
	private readonly devToolsStatusBarItem = vs.window.createStatusBarItem(vs.StatusBarAlignment.Right, 100);
	private devToolsActivationPromise: Promise<void> | undefined;
	public get devToolsActivation() { return this.devToolsActivationPromise; }

	/// Resolves to the DevTools URL. This is created immediately when a new process is being spawned so that
	/// concurrent launches can wait on the same promise.
	private devtoolsUrl: Thenable<string> | undefined;

	constructor(private readonly logger: Logger, private readonly workspaceContext: DartWorkspaceContext, private readonly debugCommands: DebugCommands, private readonly analytics: Analytics, private readonly pubGlobal: PubGlobal) {
		this.disposables.push(this.devToolsStatusBarItem);

		if (workspaceContext.workspaceConfig?.activateDevToolsEagerly) {
			this.preActivate(true).then(
				() => { this.logger.info(`Finished background activating DevTools`); },
				(e) => {
					this.logger.error("Failed to background activate DevTools");
					this.logger.error(e);
					vs.window.showErrorMessage(`Failed to activate DevTools: ${e}`);
				});
		}
	}

	private async preActivate(silent: boolean): Promise<void> {
		this.devToolsActivationPromise = this.pubGlobal.backgroundActivate(devtoolsPackageName, devtoolsPackageID, silent, this.workspaceContext.workspaceConfig?.devtoolsActivateScript);
		await this.devToolsActivationPromise;
	}

	/// Spawns DevTools and returns the full URL to open for that session
	///   eg. http://127.0.0.1:8123/?port=8543
	public async spawnForSession(session: DartDebugSessionInformation & { vmServiceUri: string }, reuseWindows: boolean, notify: boolean, page: string | undefined): Promise<{ url: string, dispose: () => void } | undefined> {
		this.analytics.logDebuggerOpenDevTools();

		// If we're mid-silent-activation, wait until that's finished.
		await this.devToolsActivationPromise;

		if (!this.devtoolsUrl) {
			// Don't try to check for install when we run eagerly.
			if (!this.workspaceContext.workspaceConfig?.activateDevToolsEagerly) {
				const isAvailable = await this.pubGlobal.promptToInstallIfRequired(devtoolsPackageName, devtoolsPackageID, undefined, "0.1.10", this.workspaceContext.workspaceConfig?.devtoolsActivateScript, true);
				if (!isAvailable) {
					return undefined;
				}
			}

			this.devtoolsUrl = vs.window.withProgress({
				location: vs.ProgressLocation.Notification,
				title: "Starting Dart DevTools...",
			}, async (_) => this.startServer());
		}
		try {
			const url = await this.devtoolsUrl;
			await vs.window.withProgress({
				location: vs.ProgressLocation.Notification,
				title: "Opening Dart DevTools...",
			}, async (_) => {
				const queryParams: { [key: string]: string | undefined } = {
					hide: "debugger",
					ide: "VSCode",
					theme: config.useDevToolsDarkTheme ? "dark" : undefined,
				};
				const canLaunchDevToolsThroughService = isRunningLocally
					&& !process.env.DART_CODE_IS_TEST_RUN
					&& await waitFor(() => this.debugCommands.vmServices.serviceIsRegistered(VmService.LaunchDevTools), 500);
				if (canLaunchDevToolsThroughService) {
					try {
						await session.session.customRequest(
							"service",
							{
								params: {
									notify,
									page,
									queryParams,
{'fraction_non_alphanumeric': 0.11960975609756097, 'fraction_numerical': 0.0046829268292682925, 'mean_word_length': 4.254228600717581, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, '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': '2738211', 'n_tokens_mistral': 3202, 'n_tokens_neox': 2836, 'n_words': 828}