src/vs/workbench/contrib/debug/common/debugUtils.ts

445 LOC · 88 covered · 357 uncovered · 22 ranges · 21 concepts · 2 introducers · 12 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/workbench/contrib/debug/common/debugger.ts · 325 LOCcommon/debugger.tssrc/vs/workbench/services/configurationResolver/common/configurationResolverUtils.ts · 12 LOCcommon/configurationReso…debugModel.ts ×5 · 39 introduced LOCdebugModel.ts ×5debugModel.ts ×1 · 13 introduced LOCdebugModel.ts ×1debugModel.ts ×1 · 8 introduced LOCdebugModel.ts ×1debugModel.ts ×6 · 20 introduced LOCdebugModel.ts ×6debugStorage.ts ×17 · 98 introduced LOCdebugStorage.ts ×17debugModel.ts ×3 · 29 introduced LOCdebugModel.ts ×3debugModel.ts ×2 · 11 introduced LOCdebugModel.ts ×2debugModel.ts ×2 · 12 introduced LOCdebugModel.ts ×2debugModel.ts ×2 · 13 introduced LOCdebugModel.ts ×2mockDebug.ts ×1 · 2 introduced LOCmockDebug.ts ×1mockDebug.ts ×1 · 2 introduced LOCmockDebug.ts ×1mockDebug.ts ×7 · 30 introduced LOCmockDebug.ts ×7abstractDebugAdapter.ts ×4 · 18 introduced LOCabstractDebugAdapter.ts …debugModel.ts ×1 · 1 introduced LOCdebugModel.ts ×1debugModel.ts ×185 · 988 introduced LOCdebugModel.ts ×185debugAdapter.ts ×16 · 67 introduced LOCdebugAdapter.ts ×16debugger.ts ×3 · 27 introduced LOCdebugger.ts ×3debugger.ts ×5 · 89 introduced LOCdebugger.ts ×5debugger.ts ×2 · 4 introduced LOCdebugger.ts ×2debugger.ts ×26 · 153 introduced LOCdebugger.ts ×26debugUtils.ts ×21 · 86 introduced LOCdebugUtils.ts ×21abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering does not insert boundaries between events|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering does not insert boundaries between events|occurrence=1abstractDebugAdapter.tes…abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering inserts task boundary after response|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering inserts task boundary after response|occurrence=1abstractDebugAdapter.tes…abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering inserts task boundary before response|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering inserts task boundary before response|occurrence=1abstractDebugAdapter.tes…debugModel.test|title=DebugModel DebugModel refreshTopOfCallstack resolves all returned promises when called multiple times|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel DebugModel refreshTopOfCallstack resolves all returned promises when called multiple times|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel ExceptionBreakpoint Restored matches new|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel ExceptionBreakpoint Restored matches new|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel FunctionBreakpoint Id is saved|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel FunctionBreakpoint Id is saved|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints falls back to instructionReference+offset when address not supplied|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints falls back to instructionReference+offset when address not supplied|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints prefers address match when instructionReference has changed|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints prefers address match when instructionReference has changed|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints with only address removes the matching entry and leaves others|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints with only address removes the matching entry and leaves others|occurrence=1debugModel.test|title=De…debugger.test|title=Debug - Debugger attributes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/node/debugger.test|title=Debug - Debugger attributes|occurrence=1debugger.test|title=Debu…debugger.test|title=Debug - Debugger getSchemaAttributes logs malformed properties and ignores them|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/node/debugger.test|title=Debug - Debugger getSchemaAttributes logs malformed properties and ignores them|occurrence=1debugger.test|title=Debu…debugger.test|title=Debug - Debugger initial config file content|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/node/debugger.test|title=Debug - Debugger initial config file content|occurrence=1debugger.test|title=Debu…Focused file · src/vs/workbench/contrib/debug/common/debugUtils.ts · 445 LOCcommon/debugUtils.ts

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- debugUtils.ts ×21
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { equalsIgnoreCase } from '../../../../base/common/strings.js';
7 > import { IDebuggerContribution, IDebugSession, IConfig, IConfigPresentation, State } from './debug.js';
8 > import { URI as uri } from '../../../../base/common/uri.js';
9 > import { isAbsolute } from '../../../../base/common/path.js';
10 > import { deepClone } from '../../../../base/common/objects.js';
11 > import { Schemas } from '../../../../base/common/network.js';
12 > import { IEditorService } from '../../../services/editor/common/editorService.js';
13 > import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
14 > import { ITextModel } from '../../../../editor/common/model.js';
15 > import { Position } from '../../../../editor/common/core/position.js';
16 > import { IRange, Range } from '../../../../editor/common/core/range.js';
17 > import { CancellationToken } from '../../../../base/common/cancellation.js';
18 > import { coalesce } from '../../../../base/common/arrays.js';
19 > import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
20 > import { OperatingSystem, OS } from '../../../../base/common/platform.js';
21 >
22 > const _formatPIIRegexp = /{([^}]+)}/g;
23 >
24 > export function formatPII(value: string, excludePII: boolean, args: { [key: string]: string } | undefined): string {
25 return value.replace(_formatPIIRegexp, function (match, group) {
26 if (excludePII && group.length > 0 && group[0] !== '_') {
27 return match;
28 }
29
30 return args && args.hasOwnProperty(group) ?
31 args[group] :
32 match;
33 });
34 }
36 > /**
37 > * Filters exceptions (keys marked with "!") from the given object. Used to
38 > * ensure exception data is not sent on web remotes, see #97628.
39 > */
40 > export function filterExceptionsFromTelemetry<T extends { [key: string]: unknown }>(data: T): Partial<T> {
41 const output: Partial<T> = {};
42 for (const key of Object.keys(data) as (keyof T & string)[]) {
43 if (!key.startsWith('!')) {
44 output[key] = data[key];
45 }
46 }
47
48 return output;
49 }
51 >
52 > export function isSessionAttach(session: IDebugSession): boolean {
53 return session.configuration.request === 'attach' && !getExtensionHostDebugSession(session) && (!session.parentSession || isSessionAttach(session.parentSession));
54 }
56 > /**
57 > * Returns the session or any parent which is an extension host debug session.
58 > * Returns undefined if there's none.
59 > */
60 > export function getExtensionHostDebugSession(session: IDebugSession): IDebugSession | void {
61 let type = session.configuration.type;
62 if (!type) {
63 return;
64 }
65
66 if (type === 'vslsShare') {
67 type = (session.configuration as { adapterProxy?: { configuration?: { type?: string } } }).adapterProxy?.configuration?.type || type;
68 }
69
70 if (equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost')) {
71 return session;
72 }
73
74 return session.parentSession ? getExtensionHostDebugSession(session.parentSession) : undefined;
75 }
77 > // only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution
78 > export function isDebuggerMainContribution(dbg: IDebuggerContribution) {
79 > return dbg.type && (dbg.label || dbg.program || dbg.runtime); debugger.ts ×26
80 > }
82 > /**
83 > * Note- uses 1-indexed numbers
84 > */
85 > export function getExactExpressionStartAndEnd(lineContent: string, looseStart: number, looseEnd: number): { start: number; end: number } {
86 let matchingExpression: string | undefined = undefined;
87 let startOffset = 0;
88
89 // Some example supported expressions: myVar.prop, a.b.c.d, myVar?.prop, myVar->prop, MyClass::StaticProp, *myVar, ...foo
90 // Match any character except a set of characters which often break interesting sub-expressions
91 const expression: RegExp = /([^()\[\]{}<>\s+\-/%~#^;=|,`!]|\->)+/g;
92 let result: RegExpExecArray | null = null;
93
94 // First find the full expression under the cursor
95 while (result = expression.exec(lineContent)) {
96 const start = result.index + 1;
97 const end = start + result[0].length;
98
99 if (start <= looseStart && end >= looseEnd) {
100 matchingExpression = result[0];
101 startOffset = start;
102 break;
103 }
104 }
105
106 // Handle spread syntax: if the expression starts with '...', extract just the identifier
107 if (matchingExpression) {
108 const spreadMatch = matchingExpression.match(/^\.\.\.(.+)/);
109 if (spreadMatch) {
110 matchingExpression = spreadMatch[1];
111 startOffset += 3; // Skip the '...' prefix
112 }
113 }
114
115 // If there are non-word characters after the cursor, we want to truncate the expression then.
116 // For example in expression 'a.b.c.d', if the focus was under 'b', 'a.b' would be evaluated.
117 if (matchingExpression) {
118 const subExpression: RegExp = /(\w|\p{L})+/gu;
119 let subExpressionResult: RegExpExecArray | null = null;
120 while (subExpressionResult = subExpression.exec(matchingExpression)) {
121 const subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length;
122 if (subEnd >= looseEnd) {
123 break;
124 }
125 }
126
127 if (subExpressionResult) {
128 matchingExpression = matchingExpression.substring(0, subExpression.lastIndex);
129 }
130 }
131
132 return matchingExpression ?
133 { start: startOffset, end: startOffset + matchingExpression.length - 1 } :
134 { start: 0, end: 0 };
135 }
137 export async function getEvaluatableExpressionAtPosition(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: Position, token?: CancellationToken): Promise<{ range: IRange; matchingExpression: string } | null> {
138 if (languageFeaturesService.evaluatableExpressionProvider.has(model)) {
139 const supports = languageFeaturesService.evaluatableExpressionProvider.ordered(model);
140
141 const results = coalesce(await Promise.all(supports.map(async support => {
142 try {
143 return await support.provideEvaluatableExpression(model, position, token ?? CancellationToken.None);
144 } catch (err) {
145 return undefined;
146 }
147 })));
148
149 if (results.length > 0) {
150 let matchingExpression = results[0].expression;
151 const range = results[0].range;
152
153 if (!matchingExpression) {
154 const lineContent = model.getLineContent(position.lineNumber);
155 matchingExpression = lineContent.substring(range.startColumn - 1, range.endColumn - 1);
156 }
157
158 return { range, matchingExpression };
159 }
160 } else { // old one-size-fits-all strategy
161 const lineContent = model.getLineContent(position.lineNumber);
162 const { start, end } = getExactExpressionStartAndEnd(lineContent, position.column, position.column);
163
164 // use regex to extract the sub-expression #9821
165 const matchingExpression = lineContent.substring(start - 1, end);
166 return {
167 matchingExpression,
168 range: new Range(position.lineNumber, start, position.lineNumber, start + matchingExpression.length)
169 };
170 }
171
172 return null;
173 }
175 > // RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt
176 > const _schemePattern = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/;
177 >
178 > export function isUriString(s: string | undefined): boolean {
179 // heuristics: a valid uri starts with a scheme and
180 // the scheme has at least 2 characters so that it doesn't look like a drive letter.
181 return !!(s && s.match(_schemePattern));
182 }
184 function stringToUri(source: PathContainer): string | undefined {
185 if (typeof source.path === 'string') {
186 if (typeof source.sourceReference === 'number' && source.sourceReference > 0) {
187 // if there is a source reference, don't touch path
188 } else {
189 if (isUriString(source.path)) {
190 return <string><unknown>uri.parse(source.path);
191 } else {
192 // assume path
193 if (isAbsolute(source.path)) {
194 return <string><unknown>uri.file(source.path);
195 } else {
196 // leave relative path as is
197 }
198 }
199 }
200 }
201 return source.path;
202 }
204 function uriToString(source: PathContainer): string | undefined {
205 if (typeof source.path === 'object') {
206 const u = uri.revive(source.path);
207 if (u) {
208 if (u.scheme === Schemas.file) {
209 return u.fsPath;
210 } else {
211 return u.toString();
212 }
213 }
214 }
215 return source.path;
216 }
218 > // path hooks helpers
219 >
220 > interface PathContainer {
221 > path?: string;
222 > sourceReference?: number;
223 > }
224 >
225 > export function convertToDAPaths(message: DebugProtocol.ProtocolMessage, toUri: boolean): DebugProtocol.ProtocolMessage {
226
227 const fixPath = toUri ? stringToUri : uriToString;
228
229 // since we modify Source.paths in the message in place, we need to make a copy of it (see #61129)
230 const msg = deepClone(message);
231
232 convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => {
233 if (toDA && source) {
234 source.path = fixPath(source);
235 }
236 });
237 return msg;
238 }
240 > export function convertToVSCPaths(message: DebugProtocol.ProtocolMessage, toUri: boolean): DebugProtocol.ProtocolMessage {
241
242 const fixPath = toUri ? stringToUri : uriToString;
243
244 // since we modify Source.paths in the message in place, we need to make a copy of it (see #61129)
245 const msg = deepClone(message);
246
247 convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => {
248 if (!toDA && source) {
249 source.path = fixPath(source);
250 }
251 });
252 return msg;
253 }
255 function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: boolean, source: PathContainer | undefined) => void): void {
256
257 switch (msg.type) {
258 case 'event': {
259 const event = <DebugProtocol.Event>msg;
260 switch (event.event) {
261 case 'output':
262 fixSourcePath(false, (<DebugProtocol.OutputEvent>event).body.source);
263 break;
264 case 'loadedSource':
265 fixSourcePath(false, (<DebugProtocol.LoadedSourceEvent>event).body.source);
266 break;
267 case 'breakpoint':
268 fixSourcePath(false, (<DebugProtocol.BreakpointEvent>event).body.breakpoint.source);
269 break;
270 default:
271 break;
272 }
273 break;
274 }
275 case 'request': {
276 const request = <DebugProtocol.Request>msg;
277 switch (request.command) {
278 case 'setBreakpoints':
279 fixSourcePath(true, (<DebugProtocol.SetBreakpointsArguments>request.arguments).source);
280 break;
281 case 'breakpointLocations':
282 fixSourcePath(true, (<DebugProtocol.BreakpointLocationsArguments>request.arguments).source);
283 break;
284 case 'source':
285 fixSourcePath(true, (<DebugProtocol.SourceArguments>request.arguments).source);
286 break;
287 case 'gotoTargets':
288 fixSourcePath(true, (<DebugProtocol.GotoTargetsArguments>request.arguments).source);
289 break;
290 case 'launchVSCode':
291 request.arguments.args.forEach((arg: PathContainer | undefined) => fixSourcePath(false, arg));
292 break;
293 default:
294 break;
295 }
296 break;
297 }
298 case 'response': {
299 const response = <DebugProtocol.Response>msg;
300 if (response.success && response.body) {
301 switch (response.command) {
302 case 'stackTrace':
303 (<DebugProtocol.StackTraceResponse>response).body.stackFrames.forEach(frame => fixSourcePath(false, frame.source));
304 break;
305 case 'loadedSources':
306 (<DebugProtocol.LoadedSourcesResponse>response).body.sources.forEach(source => fixSourcePath(false, source));
307 break;
308 case 'scopes':
309 (<DebugProtocol.ScopesResponse>response).body.scopes.forEach(scope => fixSourcePath(false, scope.source));
310 break;
311 case 'setFunctionBreakpoints':
312 (<DebugProtocol.SetFunctionBreakpointsResponse>response).body.breakpoints.forEach(bp => fixSourcePath(false, bp.source));
313 break;
314 case 'setBreakpoints':
315 (<DebugProtocol.SetBreakpointsResponse>response).body.breakpoints.forEach(bp => fixSourcePath(false, bp.source));
316 break;
317 case 'disassemble':
318 {
319 const di = <DebugProtocol.DisassembleResponse>response;
320 di.body?.instructions.forEach(di => fixSourcePath(false, di.location));
321 }
322 break;
323 case 'locations':
324 fixSourcePath(false, (<DebugProtocol.LocationsResponse>response).body?.source);
325 break;
326 default:
327 break;
328 }
329 }
330 break;
331 }
332 }
333 }
334 > export function getVisibleAndSorted<T extends { presentation?: IConfigPresentation }>(array: T[]): T[] { debugUtils.ts ×21
335 return array.filter(config => !config.presentation?.hidden).sort((first, second) => {
336 if (!first.presentation) {
337 if (!second.presentation) {
338 return 0;
339 }
340 return 1;
341 }
342 if (!second.presentation) {
343 return -1;
344 }
345 if (!first.presentation.group) {
346 if (!second.presentation.group) {
347 return compareOrders(first.presentation.order, second.presentation.order);
348 }
349 return 1;
350 }
351 if (!second.presentation.group) {
352 return -1;
353 }
354 if (first.presentation.group !== second.presentation.group) {
355 return first.presentation.group.localeCompare(second.presentation.group);
356 }
357
358 return compareOrders(first.presentation.order, second.presentation.order);
359 });
360 }
362 function compareOrders(first: number | undefined, second: number | undefined): number {
363 if (typeof first !== 'number') {
364 if (typeof second !== 'number') {
365 return 0;
366 }
367
368 return 1;
369 }
370 if (typeof second !== 'number') {
371 return -1;
372 }
373
374 return first - second;
375 }
377 export async function saveAllBeforeDebugStart(configurationService: IConfigurationService, editorService: IEditorService): Promise<void> {
378 const saveBeforeStartConfig: string = configurationService.getValue('debug.saveBeforeStart', { overrideIdentifier: editorService.activeTextEditorLanguageId });
379 if (saveBeforeStartConfig !== 'none') {
380 await editorService.saveAll();
381 if (saveBeforeStartConfig === 'allEditorsInActiveGroup') {
382 const activeEditor = editorService.activeEditorPane;
383 if (activeEditor && activeEditor.input.resource?.scheme === Schemas.untitled) {
384 // Make sure to save the active editor in case it is in untitled file it wont be saved as part of saveAll #111850
385 await editorService.save({ editor: activeEditor.input, groupId: activeEditor.group.id });
386 }
387 }
388 }
389 await configurationService.reloadConfiguration();
390 }
392 > export const sourcesEqual = (a: DebugProtocol.Source | undefined, b: DebugProtocol.Source | undefined): boolean =>
393 !a || !b ? a === b : a.name === b.name && a.path === b.path && a.sourceReference === b.sourceReference;
395 > /**
396 > * Resolves the best child session to focus when a parent session is selected.
397 > * Always prefer child sessions over parent wrapper sessions to ensure console responsiveness.
398 > * Fixes issue #152407: Using debug console picker when not paused leaves console unresponsive.
399 > */
400 > export function resolveChildSession(session: IDebugSession, allSessions: readonly IDebugSession[]): IDebugSession {
401 // Always focus child session instead of parent wrapper session #152407
402 const childSessions = allSessions.filter(s => s.parentSession === session);
403 if (childSessions.length > 0) {
404 // Prefer stopped child session if available #112595
405 const stoppedChildSession = childSessions.find(s => s.state === State.Stopped);
406 if (stoppedChildSession) {
407 return stoppedChildSession;
408 } else {
409 // If no stopped child, focus the first available child session
410 return childSessions[0];
411 }
412 }
413 // Return the original session if it has no children
414 return session;
415 }
417 > type IPlatformSpecificConfig = NonNullable<IConfig['windows']>;
418 >
419 function getPlatformSpecificConfig(config: IConfig, os: OperatingSystem): IPlatformSpecificConfig | undefined {
420 switch (os) {
421 case OperatingSystem.Windows:
422 return config.windows;
423 case OperatingSystem.Macintosh:
424 return config.osx;
425 case OperatingSystem.Linux:
426 return config.linux;
427 }
428 }
430 > export function getEffectiveConfigForPlatform(config: IConfig, os: OperatingSystem = OS): IConfig {
431 const platformConfig = getPlatformSpecificConfig(config, os);
432 if (!platformConfig) {
433 return config;
434 }
435
436 return {
437 ...config,
438 ...platformConfig,
439 presentation: platformConfig.presentation ? { ...config.presentation, ...platformConfig.presentation } : config.presentation,
440 };
441 }
443 > export function getEffectivePresentationForConfig(config: IConfig, os: OperatingSystem = OS): IConfigPresentation | undefined {
444 return getEffectiveConfigForPlatform(config, os).presentation;
445 }