|
17 | 17 | import * as path from 'node:path'; |
18 | 18 | import * as vscode from 'vscode'; |
19 | 19 | import { constructor } from '../generic/constructor'; |
20 | | -import { MANAGE_COMPONENTS_PACKS_COMMAND_ID } from '../manifest'; |
| 20 | +import { MANAGE_COMPONENTS_PACKS_COMMAND_ID, MERGE_FILE_FROM_PATH_COMMAND_ID } from '../manifest'; |
21 | 21 | import { LogMessages } from '../json-rpc/csolution-rpc-client'; |
22 | 22 | import * as fsUtils from '../utils/fs-utils'; |
23 | 23 | import { getFileNameFromPath } from '../utils/path-utils'; |
24 | | -import { stripTwoExtensions } from '../utils/string-utils'; |
| 24 | +import { stripTwoExtensions, stripVendor, stripVersion } from '../utils/string-utils'; |
25 | 25 | import { getWorkspaceFolder } from '../utils/vscode-utils'; |
26 | 26 | import { SolutionLoadStateChangeEvent, SolutionManager } from './solution-manager'; |
27 | 27 | import { ConvertResultData, SolutionEventHub } from './solution-event-hub'; |
@@ -101,6 +101,17 @@ export const enrichLogMessagesFromToolOutput = async (logMessages: LogMessages, |
101 | 101 | warnings.forEach(w => pushUniquely(logWarnings, w)); |
102 | 102 | }; |
103 | 103 |
|
| 104 | +export const MERGE_VIEW_LINK_LABEL = 'Open in Merge View'; |
| 105 | +export type MergeUpdateLevel = 'required' | 'recommended' | 'suggested' | 'mandatory'; |
| 106 | +const mergeMessageRegex = /file\s+'([^']+)'\s+update\s+(required|recommended|suggested|mandatory)/i; |
| 107 | +const mergeComponentRegex = /(?:for|from)\s+component\s+'([^']+)'/i; |
| 108 | +export interface MergeMessageMatch { |
| 109 | + localPath: string; |
| 110 | + updateLevel: MergeUpdateLevel; |
| 111 | + matchStart: number; |
| 112 | + matchLength: number; |
| 113 | +} |
| 114 | + |
104 | 115 | export interface SolutionProblems { |
105 | 116 | activate(context: vscode.ExtensionContext): Promise<void>; |
106 | 117 | } |
@@ -190,12 +201,15 @@ export class SolutionProblemsImpl implements SolutionProblems { |
190 | 201 | if (!file) { |
191 | 202 | return false; |
192 | 203 | } |
| 204 | + const mergeAction = this.createMergeDiagnosticAction(messageText, file); |
193 | 205 | const range = await this.createDiagnosticRange(file, filename, line, column); |
194 | 206 |
|
195 | | - const entry = new vscode.Diagnostic(range, messageText, severity); |
| 207 | + const entry = new vscode.Diagnostic(range, mergeAction?.message ?? messageText, severity); |
196 | 208 | entry.source = 'csolution'; |
197 | 209 |
|
198 | | - if (!line && !column) { |
| 210 | + if (mergeAction) { |
| 211 | + entry.code = mergeAction.code; |
| 212 | + } else if (!line && !column) { |
199 | 213 | // add 'Find in Files' action only if no line/column info is available |
200 | 214 | entry.code = this.createDiagnosticActionCode(messageText); |
201 | 215 | } |
@@ -296,6 +310,55 @@ export class SolutionProblemsImpl implements SolutionProblems { |
296 | 310 | return undefined; |
297 | 311 | } |
298 | 312 |
|
| 313 | + private parseMergeMessage(line: string): MergeMessageMatch | undefined { |
| 314 | + const match = mergeMessageRegex.exec(line); |
| 315 | + if (!match || match.index === undefined) { |
| 316 | + return undefined; |
| 317 | + } |
| 318 | + |
| 319 | + return { |
| 320 | + localPath: match[1], |
| 321 | + updateLevel: match[2].toLowerCase() as MergeUpdateLevel, |
| 322 | + matchStart: match.index, |
| 323 | + matchLength: match[0].length, |
| 324 | + }; |
| 325 | + } |
| 326 | + |
| 327 | + private createMergeDiagnosticMessage(localPath: string, updateLevel: MergeUpdateLevel, componentId: string | undefined): string { |
| 328 | + const fileName = path.basename(localPath); |
| 329 | + if (componentId === undefined) { |
| 330 | + return `update ${updateLevel} for config file '${fileName}' has a new version available for merge.`; |
| 331 | + } |
| 332 | + |
| 333 | + const componentIdNoVersion = stripVersion(componentId); |
| 334 | + const componentDisplayName = stripVendor(componentIdNoVersion); |
| 335 | + return `update ${updateLevel} for config file '${fileName}' from component '${componentDisplayName}'.`; |
| 336 | + } |
| 337 | + |
| 338 | + private createMergeCommandUri(localPath: string): vscode.Uri { |
| 339 | + const args = this.encodeCommandArgs([localPath]); |
| 340 | + return vscode.Uri.parse(`command:${MERGE_FILE_FROM_PATH_COMMAND_ID}?${args}`); |
| 341 | + } |
| 342 | + |
| 343 | + private createMergeDiagnosticAction(message: string, diagnosticFilePath: string): { message: string; code: NonNullable<vscode.Diagnostic['code']> } | undefined { |
| 344 | + const merge = this.parseMergeMessage(message); |
| 345 | + if (!merge) { |
| 346 | + return undefined; |
| 347 | + } |
| 348 | + |
| 349 | + const componentId = mergeComponentRegex.exec(message)?.[1]; |
| 350 | + const localPath = path.isAbsolute(merge.localPath) ? merge.localPath : diagnosticFilePath; |
| 351 | + const formattedMessage = this.createMergeDiagnosticMessage(localPath, merge.updateLevel, componentId); |
| 352 | + |
| 353 | + return { |
| 354 | + message: formattedMessage, |
| 355 | + code: { |
| 356 | + value: MERGE_VIEW_LINK_LABEL, |
| 357 | + target: this.createMergeCommandUri(localPath), |
| 358 | + }, |
| 359 | + }; |
| 360 | + } |
| 361 | + |
299 | 362 | private encodeFindInFilesArgs(query: string): string { |
300 | 363 | const args = { |
301 | 364 | query: query, |
|
0 commit comments