/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { equalsIgnoreCase } from 'vs/base/common/strings'; import { IConfig, IDebuggerContribution, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { URI as uri } from 'vs/base/common/uri'; import { isAbsolute } from 'vs/base/common/path'; import { deepClone } from 'vs/base/common/objects'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { first } from 'vs/base/common/arrays'; const _formatPIIRegexp = /{([^}]+)}/g; export function startDebugging(debugService: IDebugService, historyService: IHistoryService, noDebug: boolean, ): Promise { const configurationManager = debugService.getConfigurationManager(); let launch = configurationManager.selectedConfiguration.launch; if (!launch || launch.getConfigurationNames().length === 0) { const rootUri = historyService.getLastActiveWorkspaceRoot(); launch = configurationManager.getLaunch(rootUri); if (!launch || launch.getConfigurationNames().length === 0) { const launches = configurationManager.getLaunches(); launch = first(launches, l => !!(l && l.getConfigurationNames().length), launch); } configurationManager.selectConfiguration(launch); } return debugService.startDebugging(launch, undefined, noDebug); } export function formatPII(value: string, excludePII: boolean, args: { [key: string]: string }): string { return value.replace(_formatPIIRegexp, function (match, group) { if (excludePII && group.length > 0 && group[0] !== '_') { return match; } return args && args.hasOwnProperty(group) ? args[group] : match; }); } export function isExtensionHostDebugging(config: IConfig) { return config.type && equalsIgnoreCase(config.type === 'vslsShare' ? (config).adapterProxy.configuration.type : config.type, 'extensionhost'); } // only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution export function isDebuggerMainContribution(dbg: IDebuggerContribution) { return dbg.type && (dbg.label || dbg.program || dbg.runtime); } export function getExactExpressionStartAndEnd(lineContent: string, looseStart: number, looseEnd: number): { start: number, end: number } { let matchingExpression: string | undefined = undefined; let startOffset = 0; // Some example supported expressions: myVar.prop, a.b.c.d, myVar?.prop, myVar->prop, MyClass::StaticProp, *myVar // Match any character except a set of characters which often break interesting sub-expressions let expression: RegExp = /([^()\[\]{}<>\s+\-/%~#^;=|,`!]|\->)+/g; let result: RegExpExecArray | null = null; // First find the full expression under the cursor while (result = expression.exec(lineContent)) { let start = result.index + 1; let end = start + result[0].length; if (start <= looseStart && end >= looseEnd) { matchingExpression = result[0]; startOffset = start; break; } } // If there are non-word characters after the cursor, we want to truncate the expression then. // For example in expression 'a.b.c.d', if the focus was under 'b', 'a.b' would be evaluated. if (matchingExpression) { let subExpression: RegExp = /\w+/g; let subExpressionResult: RegExpExecArray | null = null; while (subExpressionResult = subExpression.exec(matchingExpression)) { let subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length; if (subEnd >= looseEnd) { break; } } if (subExpressionResult) { matchingExpression = matchingExpression.substring(0, subExpression.lastIndex); } } return matchingExpression ? { start: startOffset, end: startOffset + matchingExpression.length - 1 } : { start: 0, end: 0 }; } // RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt const _schemePattern = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/; export function isUri(s: string | undefined): boolean { // heuristics: a valid uri starts with a scheme and // the scheme has at least 2 characters so that it doesn't look like a drive letter. return !!(s && s.match(_schemePattern)); } function stringToUri(path: string): string { if (typeof path === 'string') { if (isUri(path)) { return uri.parse(path); } else { // assume path if (isAbsolute(path)) { return uri.file(path); } else { // leave relative path as is } } } return path; } function uriToString(path: string): string { if (typeof path === 'object') { const u = uri.revive(path); if (u.scheme === 'file') { return u.fsPath; } else { return u.toString(); } } return path; } // path hooks helpers interface PathContainer { path?: string; } export function convertToDAPaths(message: DebugProtocol.ProtocolMessage, toUri: boolean): DebugProtocol.ProtocolMessage { const fixPath = toUri ? stringToUri : uriToString; // since we modify Source.paths in the message in place, we need to make a copy of it (see #61129) const msg = deepClone(message); convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => { if (toDA && source) { source.path = source.path ? fixPath(source.path) : undefined; } }); return msg; } export function convertToVSCPaths(message: DebugProtocol.ProtocolMessage, toUri: boolean): DebugProtocol.ProtocolMessage { const fixPath = toUri ? stringToUri : uriToString; // since we modify Source.paths in the message in place, we need to make a copy of it (see #61129) const msg = deepClone(message); convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => { if (!toDA && source) { source.path = source.path ? fixPath(source.path) : undefined; } }); return msg; } function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: boolean, source: PathContainer | undefined) => void): void { switch (msg.type) { case 'event': const event = msg; switch (event.event) { case 'output': fixSourcePath(false, (event).body.source); break; case 'loadedSource': fixSourcePath(false, (event).body.source); break; case 'breakpoint': fixSourcePath(false, (event).body.breakpoint.source); break; default: break; } break; case 'request': const request = msg; switch (request.command) { case 'setBreakpoints': fixSourcePath(true, (request.arguments).source); break; case 'source': fixSourcePath(true, (request.arguments).source); break; case 'gotoTargets': fixSourcePath(true, (request.arguments).source); break; case 'launchVSCode': request.arguments.args.forEach(arg => fixSourcePath(false, arg)); break; default: break; } break; case 'response': const response = msg; if (response.success) { switch (response.command) { case 'stackTrace': (response).body.stackFrames.forEach(frame => fixSourcePath(false, frame.source)); break; case 'loadedSources': (response).body.sources.forEach(source => fixSourcePath(false, source)); break; case 'scopes': (response).body.scopes.forEach(scope => fixSourcePath(false, scope.source)); break; case 'setFunctionBreakpoints': (response).body.breakpoints.forEach(bp => fixSourcePath(false, bp.source)); break; case 'setBreakpoints': (response).body.breakpoints.forEach(bp => fixSourcePath(false, bp.source)); break; default: break; } } break; } }