Skip to content

Commit bf34def

Browse files
edrioukCopilot
andauthored
Solution outline: use RPC data (#64)
* Solution outline: use RPC data * Lint fix * Add access sequences support to outline tree * Order of RPC commands * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Correct test reference data according to changes * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix copyright comment formatting in solution-rpc-data.factory.ts --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent f49d185 commit bf34def

16 files changed

Lines changed: 259 additions & 117 deletions

src/solutions/solution-manager.factories.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { faker } from '@faker-js/faker';
2121
import path from 'path';
2222
import { csolutionFactory } from './csolution.factory';
2323
import { Severity } from './constants';
24+
import { solutionRpcDataFactory } from './solution-rpc-data.factory';
2425

2526
export type MockSolutionManager = jest.Mocked<StubEvents<SolutionManager>> & { fireOnDidChangeLoadState: ReturnType<typeof fireOnDidChangeLoadState> };
2627

@@ -50,6 +51,7 @@ const fireOnDidChangeLoadState = (emitter: vscode.EventEmitter<SolutionLoadState
5051
export const solutionManagerFactory = makeFactory<MockSolutionManager>({
5152
loadState: () => idleSolutionLoadStateFactory(),
5253
getCsolution: () => jest.fn().mockReturnValue(csolutionFactory()),
54+
getRpcData: () => jest.fn().mockReturnValue(solutionRpcDataFactory()),
5355
onDidChangeLoadStateEmitter: () => new vscode.EventEmitter<SolutionLoadStateChangeEvent>(),
5456
onDidChangeLoadState: (r) => jest.fn(r.onDidChangeLoadStateEmitter!.event),
5557
onLoadedBuildFilesEmitter: () => new vscode.EventEmitter<[Severity, boolean]>(),

src/solutions/solution-manager.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export interface SolutionManager {
5757

5858
readonly getCsolution: () => CSolution | undefined;
5959

60+
readonly getRpcData: () => SolutionRpcData | undefined;
61+
6062
readonly onDidChangeLoadState: vscode.Event<SolutionLoadStateChangeEvent>;
6163

6264
readonly onLoadedBuildFiles: vscode.Event<[Severity, boolean]>;
@@ -119,6 +121,11 @@ export class SolutionManagerImpl implements SolutionManager {
119121
return this.csolution;
120122
}
121123

124+
public getRpcData(): SolutionRpcData | undefined {
125+
return this.rpcData;
126+
}
127+
128+
122129
public get loadState(): SolutionLoadState {
123130
return this._loadState;
124131
}
@@ -136,8 +143,9 @@ export class SolutionManagerImpl implements SolutionManager {
136143
if (!this.isSolutionActivated()) {
137144
return;
138145
}
139-
await this.loadSolution();
140-
this.requestConvert(false, true, false);
146+
if (await this.loadSolution()) {
147+
this.requestConvert(false, true, false);
148+
}
141149
}
142150

143151

@@ -151,9 +159,10 @@ export class SolutionManagerImpl implements SolutionManager {
151159

152160
if (solutionPath) {
153161
this.setLoadState(newState, false);
154-
await this.loadSolution();
155-
// trigger solution convert without RTE update
156-
this.requestConvert(false, false, true);
162+
if (await this.loadSolution()) {
163+
// trigger solution convert without RTE update
164+
this.requestConvert(false, false, true);
165+
}
157166
} else {
158167
this.setLoadState(newState, true);
159168
}
@@ -163,8 +172,9 @@ export class SolutionManagerImpl implements SolutionManager {
163172
if (!this.loadState.solutionPath) {
164173
return;
165174
}
166-
await this.loadSolution();
167-
this.requestConvert(true, false, false);
175+
if (await this.loadSolution()) {
176+
this.requestConvert(true, false, false);
177+
}
168178
}
169179

170180
public async refresh() {
@@ -196,9 +206,15 @@ export class SolutionManagerImpl implements SolutionManager {
196206
});
197207
}
198208

199-
private async loadSolution(): Promise<void> {
209+
private async updateRpcData() {
210+
if (this.csolution) {
211+
await this.rpcData.update(this.csolution);
212+
}
213+
}
214+
215+
private async loadSolution(): Promise<boolean> {
200216
if (this.loadingSolution || !this.loadState.solutionPath) {
201-
return;
217+
return false;
202218
}
203219
try {
204220
this.loadingSolution = true;
@@ -210,19 +226,20 @@ export class SolutionManagerImpl implements SolutionManager {
210226
...this.loadState,
211227
loaded: true
212228
};
229+
await this.updateRpcData();
213230
this.setLoadState(newState, true);
214231
} catch (error) {
215232
console.error(`Failed to load ${this.loadState.solutionPath}`, error);
216233
} finally {
217234
this.loadingSolution = false;
218235
}
236+
return true;
219237
}
220238

221239
private async handleSolutionConvertCompleted(data: ConvertResultData) {
222240
if (!this.csolution) {
223241
return;
224242
}
225-
await this.rpcData.update(this.csolution);
226243
await this.loadSolutionBuildFiles();
227244

228245
if (data.severity != 'error') {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Copyright 2026 Arm Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { csolutionServiceFactory } from '../json-rpc/csolution-rpc-client.factory';
18+
import { Board, Device, Variables } from '../json-rpc/csolution-rpc-client';
19+
import { SolutionRpcData, SolutionRpcDataImpl } from './solution-rpc-data';
20+
21+
22+
export class SolutionRpcDataMock extends SolutionRpcDataImpl {
23+
constructor() {
24+
super(csolutionServiceFactory());
25+
}
26+
27+
public seedBoard(board?: Board): SolutionRpcDataMock {
28+
this._board = board;
29+
return this;
30+
}
31+
32+
public seedDevice(device?: Device): SolutionRpcDataMock {
33+
this._device = device;
34+
return this;
35+
}
36+
37+
public seedVariables(context: string, variables: Variables): SolutionRpcDataMock {
38+
this.contextVariables.set(context, this.variablesFromRpcData(variables));
39+
return this;
40+
}
41+
}
42+
43+
export function solutionRpcDataFactory(
44+
options: Partial<SolutionRpcData> = {}
45+
): SolutionRpcDataMock {
46+
const solutionRpcDataMock = new SolutionRpcDataMock();
47+
48+
Object.assign(solutionRpcDataMock, options);
49+
50+
return solutionRpcDataMock;
51+
}

src/solutions/solution-rpc-data.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import { constructor } from '../generic/constructor';
17-
import { Board, CsolutionService, Device, VariablesResult } from '../json-rpc/csolution-rpc-client';
17+
import { Board, CsolutionService, Device, Variables } from '../json-rpc/csolution-rpc-client';
1818
import { CSolution } from './csolution';
1919

2020

@@ -34,9 +34,16 @@ export interface SolutionRpcData {
3434
*/
3535
get device(): Device | undefined;
3636

37+
/**
38+
* Returns variables for given context
39+
* @param context resolving context
40+
* @return key-value map of variables, key is surrounded with '$' chars
41+
*/
42+
getVariables(context: string): Map<string, string> | undefined;
43+
3744
/** Resolves a single variable for a context
3845
* @param context resolving context
39-
* @param variable name without surrounding '$' chars
46+
* @param variable name with surrounding '$' chars
4047
* @return variable value if resolved, undefined otherwise
4148
*/
4249
resolveVariable(context: string, variable?: string): string | undefined;
@@ -52,13 +59,13 @@ export interface SolutionRpcData {
5259
expandString(str: string, context: string): string
5360
}
5461

55-
class SolutionRpcDataImpl implements SolutionRpcData {
56-
private readonly contextVariables = new Map<string, Map<string, string>>();
57-
private _board?: Board = undefined;
58-
private _device?: Device = undefined;
62+
export class SolutionRpcDataImpl implements SolutionRpcData {
63+
protected readonly contextVariables = new Map<string, Map<string, string>>();
64+
protected _board?: Board = undefined;
65+
protected _device?: Device = undefined;
5966

6067
constructor(
61-
private readonly csolutionService: CsolutionService,
68+
protected readonly csolutionService: CsolutionService,
6269
) {
6370
}
6471
clear() {
@@ -82,6 +89,15 @@ class SolutionRpcDataImpl implements SolutionRpcData {
8289
return;
8390
}
8491
const activeTarget = activeTargetType.name;
92+
const res = await this.csolutionService.loadSolution(
93+
{
94+
solution: solution.solutionPath,
95+
activeTarget: activeTarget
96+
});
97+
if (!res.success) {
98+
return;
99+
}
100+
85101
if (activeTargetType.device) {
86102
const deviceInfo = await this.csolutionService.getDeviceInfo({ id: activeTargetType.device });
87103
if (deviceInfo.success) {
@@ -95,34 +111,30 @@ class SolutionRpcDataImpl implements SolutionRpcData {
95111
};
96112
}
97113

98-
const res = await this.csolutionService.loadSolution(
99-
{
100-
solution: solution.solutionPath,
101-
activeTarget: activeTarget
102-
});
103-
if (!res.success) {
104-
return;
105-
}
106114
const contexts = solution.getContextNames();
107115
for (const context of contexts) {
108116
const data = await this.csolutionService.getVariables({ context: context });
109117
if (data.success) {
110-
this.contextVariables.set(context, this.variablesFromRpcData(data));
118+
this.contextVariables.set(context, this.variablesFromRpcData(data.variables));
111119
}
112120
}
113121
}
114122

115-
private variablesFromRpcData(data: VariablesResult) {
123+
public getVariables(context: string): Map<string, string> | undefined {
124+
return this.contextVariables.get(context);
125+
}
126+
127+
protected variablesFromRpcData(variables: Variables) {
116128
const vars = new Map<string, string>();
117-
for (const [key, value] of Object.entries(data.variables)) {
129+
for (const [key, value] of Object.entries(variables)) {
118130
vars.set('$' + key + '$', value);
119131
}
120132
return vars;
121133
}
122134

123135
public resolveVariable(context: string, variable?: string): string | undefined {
124136
if (variable) {
125-
const variables = this.contextVariables.get(context);
137+
const variables = this.getVariables(context);
126138
if (variables) {
127139
return variables.get(variable);
128140
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ describe('DeleteCommand', () => {
8888
const groups = children?.[2];
8989
const groupItems = groups?.getChildren();
9090

91-
const want: string[] = ['README.md', 'HID.c'];
91+
const want: string[] = ['README.md', 'HID.c', '$OutDir()$/testOutput.test'];
9292
const got: string[] = [];
9393
if (groupItems) {
9494
for (const gi of groupItems) {
@@ -194,7 +194,7 @@ describe('DeleteCommand', () => {
194194
const groups = children?.[2];
195195
const groupItems = groups?.getChildren();
196196

197-
const want: string[] = ['Documentation'];
197+
const want: string[] = ['Documentation', 'AccessSequencesTest'];
198198
const got: string[] = [];
199199
if (groupItems) {
200200
for (const gi of groupItems) {

src/views/solution-outline/solution-outline.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { TreeViewProvider } from './treeview-provider';
2121
import { CsolutionGlobalState, GlobalState } from '../../vscode-api/global-state';
2222
import { SolutionOutlineTree } from './tree-structure/solution-outline-tree';
2323
import { COutlineItem } from './tree-structure/solution-outline-item';
24-
import { CSolution } from '../../solutions/csolution';
2524
import { TreeViewFileDecorationProvider } from './treeview-decoration-provider';
2625

2726
export class SolutionOutlineView {
@@ -34,7 +33,6 @@ export class SolutionOutlineView {
3433
private readonly treeViewProvider: TreeViewProvider<COutlineItem>,
3534
private readonly globalStateProvider: GlobalState<CsolutionGlobalState>,
3635
private readonly treeViewFileDecorationProvider: TreeViewFileDecorationProvider,
37-
private readonly solutionOutlineTree = new SolutionOutlineTree()
3836
) { }
3937

4038
public async activate(context: Pick<vscode.ExtensionContext, 'subscriptions' | 'globalState' | 'workspaceState'>): Promise<void> {
@@ -75,15 +73,16 @@ export class SolutionOutlineView {
7573
this.treeViewProvider.setDescription('');
7674
this.treeViewProvider.setTitle('');
7775
}
78-
this.createTree(loadState, csolution, thisTreeUpdateNumber);
76+
this.createTree(loadState, thisTreeUpdateNumber);
7977
}
8078

81-
private createTree(loadState: SolutionLoadState, csolution: CSolution | undefined, thisTreeUpdateNumber: number) {
79+
private createTree(loadState: SolutionLoadState, thisTreeUpdateNumber: number) {
8280
if (loadState.solutionPath) {
8381
if (this.treeUpdateCount !== thisTreeUpdateNumber) {
8482
return;
8583
}
86-
const tree = this.solutionOutlineTree.createTree(csolution);
84+
const solutionOutlineTree = new SolutionOutlineTree(this.solutionManager.getCsolution(), this.solutionManager.getRpcData());
85+
const tree = solutionOutlineTree.createTree();
8786
this.treeViewProvider.updateTree(tree);
8887
this.treeViewFileDecorationProvider.setTreeRoot(tree);
8988
} else if (!loadState.solutionPath) {

src/views/solution-outline/tree-structure/solution-outline-file-item.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { FileItem } from './solution-outline-file-item';
17+
import { FileItemBuilder } from './solution-outline-file-item';
1818
import { parseYamlToCTreeItem } from '../../../generic/tree-item-yaml-parser';
1919
import fs from 'fs';
2020
import os from 'os';
@@ -23,19 +23,17 @@ import { COutlineItem } from './solution-outline-item';
2323

2424

2525
describe('FileItem', () => {
26-
let fileItem: FileItem;
26+
let fileItem: FileItemBuilder;
2727
let projectDir: string;
2828
let cSolFile: string;
2929
let componentNode: COutlineItem;
3030

3131
beforeEach(async () => {
32-
fileItem = new FileItem();
33-
3432
const tmpDir = os.tmpdir();
3533
projectDir = fs.mkdtempSync(path.join(tmpDir, 'myProject'));
3634
cSolFile = `${projectDir}/Blinky.csolution.yml`;
3735

38-
fileItem = new FileItem();
36+
fileItem = new FileItemBuilder();
3937

4038
componentNode = new COutlineItem('component');
4139
componentNode.setTag('component');

0 commit comments

Comments
 (0)