Skip to content

Commit 5b73b26

Browse files
committed
Add link to open merge view
1 parent 8715d1c commit 5b73b26

7 files changed

Lines changed: 87 additions & 134 deletions

File tree

src/desktop/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export const activate = async (context: ExtensionContext): Promise<CsolutionExte
179179
cmsisToolboxManager,
180180
compileCommandsGenerator,
181181
);
182-
182+
183183
const solutionProblems = new SolutionProblemsImpl(solutionManager, eventHub);
184184
const themeProvider = new ThemeProviderImpl();
185185
const statusBar = new StatusBar(solutionManager, cmsisToolboxManager, themeProvider);

src/manifest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const OUTPUT_DIRECTORY = 'outputDirectory';
4444
export const CONFIG_AUTO_DEBUG_LAUNCH = 'autoDebugLaunch';
4545
export const CONFIG_BUILD_OUTPUT_VERBOSITY = 'buildOutputVerbosity';
4646
export const MANAGE_COMPONENTS_PACKS_COMMAND_ID = `${PACKAGE_NAME}.manageComponentsPacks`;
47+
export const MERGE_FILE_FROM_PATH_COMMAND_ID = `${PACKAGE_NAME}.mergeFileFromPath`;
4748

4849
export const MIN_TOOLBOX_VERSION = '2.12.0';
4950

src/solutions/solution-problems.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
import * as path from 'node:path';
1818
import * as vscode from 'vscode';
1919
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';
2121
import { LogMessages } from '../json-rpc/csolution-rpc-client';
2222
import * as fsUtils from '../utils/fs-utils';
2323
import { getFileNameFromPath } from '../utils/path-utils';
24-
import { stripTwoExtensions } from '../utils/string-utils';
24+
import { stripTwoExtensions, stripVendor, stripVersion } from '../utils/string-utils';
2525
import { getWorkspaceFolder } from '../utils/vscode-utils';
2626
import { SolutionLoadStateChangeEvent, SolutionManager } from './solution-manager';
2727
import { ConvertResultData, SolutionEventHub } from './solution-event-hub';
@@ -101,6 +101,17 @@ export const enrichLogMessagesFromToolOutput = async (logMessages: LogMessages,
101101
warnings.forEach(w => pushUniquely(logWarnings, w));
102102
};
103103

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+
104115
export interface SolutionProblems {
105116
activate(context: vscode.ExtensionContext): Promise<void>;
106117
}
@@ -190,12 +201,15 @@ export class SolutionProblemsImpl implements SolutionProblems {
190201
if (!file) {
191202
return false;
192203
}
204+
const mergeAction = this.createMergeDiagnosticAction(messageText, file);
193205
const range = await this.createDiagnosticRange(file, filename, line, column);
194206

195-
const entry = new vscode.Diagnostic(range, messageText, severity);
207+
const entry = new vscode.Diagnostic(range, mergeAction?.message ?? messageText, severity);
196208
entry.source = 'csolution';
197209

198-
if (!line && !column) {
210+
if (mergeAction) {
211+
entry.code = mergeAction.code;
212+
} else if (!line && !column) {
199213
// add 'Find in Files' action only if no line/column info is available
200214
entry.code = this.createDiagnosticActionCode(messageText);
201215
}
@@ -296,6 +310,55 @@ export class SolutionProblemsImpl implements SolutionProblems {
296310
return undefined;
297311
}
298312

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+
299362
private encodeFindInFilesArgs(query: string): string {
300363
const args = {
301364
query: query,

src/views/solution-outline/commands/merge-command.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,13 @@ describe('MergeCommand', () => {
8787
componentNode.setTag('component');
8888
componentNode.setAttribute('label', 'Component X');
8989
componentNode.setAttribute('local', path.join(tmpDir, 'component.c'));
90+
componentNode.setAttribute('resourcePath', path.join(tmpDir, 'component.c'));
9091

9192
fileNode = new COutlineItem('file');
9293
fileNode.setTag('file');
9394
fileNode.setAttribute('label', 'Component X');
9495
fileNode.setAttribute('local', path.join(tmpDir, 'component.c'));
96+
fileNode.setAttribute('resourcePath', path.join(tmpDir, 'component.c'));
9597
});
9698

9799
describe('activation', () => {
@@ -326,6 +328,7 @@ describe('MergeCommand', () => {
326328
const node = new COutlineItem('file');
327329
const local = path.join(tmpDir, 'safe-local.c');
328330
node.setAttribute('local', local);
331+
node.setAttribute('resourcePath', local);
329332

330333
const commandPrivate = command as unknown as {
331334
getVSCodeExecutablePath: () => string | undefined;
@@ -356,6 +359,7 @@ describe('MergeCommand', () => {
356359
node.setTag('file');
357360
node.setAttribute('label', 'Component X');
358361
node.setAttribute('local', local);
362+
node.setAttribute('resourcePath', local);
359363

360364
const commandPrivate = command as unknown as {
361365
getVSCodeExecutablePath: () => string | undefined;

src/views/solution-outline/commands/merge-command.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export class MergeCommand {
5252
vscode.window.showErrorMessage('Cannot open merge view: merge file path is missing.');
5353
return;
5454
}
55+
56+
await this.runVSCodeMergeForPath(path.normalize(localPath));
5557
}
5658

5759
private async runVSCodeMerge(node: COutlineItem): Promise<void> {
@@ -60,7 +62,17 @@ export class MergeCommand {
6062
return;
6163
}
6264

63-
const mergeFiles = this.discoverMergeFiles(node);
65+
const localPath = node.getResourcePath();
66+
if (!localPath) {
67+
vscode.window.showErrorMessage('Required local file is missing to perform merge.');
68+
return;
69+
}
70+
71+
await this.runVSCodeMergeForPath(localPath);
72+
}
73+
74+
private async runVSCodeMergeForPath(localPath: string): Promise<void> {
75+
const mergeFiles = this.discoverMergeFiles(localPath);
6476
if (!mergeFiles) {
6577
return;
6678
}
@@ -78,7 +90,6 @@ export class MergeCommand {
7890

7991
// make a copy of local to create merged file
8092
fsUtils.copyFile(local, merged);
81-
node.setAttribute('merged', merged);
8293

8394
// ensure all paths are absolute
8495
local = path.resolve(local);
@@ -112,12 +123,7 @@ export class MergeCommand {
112123
}
113124
}
114125

115-
private discoverMergeFiles(node: COutlineItem): { local: string; update: string; base: string } | undefined {
116-
const local = node.getAttribute('local');
117-
if (!local) {
118-
vscode.window.showErrorMessage('Required local file is missing to perform merge.');
119-
return undefined;
120-
}
126+
private discoverMergeFiles(local: string): { local: string; update: string; base: string } | undefined {
121127

122128
const discovered = this.findNewestMergeFiles(local);
123129
const update = discovered.update;

src/views/solution-outline/commands/merge-message-parser.test.ts

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/views/solution-outline/commands/merge-message-parser.ts

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)