Skip to content

Commit d74b9a0

Browse files
authored
Various Diagram Fixes (#349)
* feat-fix(diagram): fix highlighting of ast diagram - #345 * feat-fix(diagram): fix off by one error in highlight- #346 * feat(diagram): move simplify dataflow into a toggle - #350 * test(util): test for selection to nodeid - #347
1 parent 672b142 commit d74b9a0

File tree

6 files changed

+107
-34
lines changed

6 files changed

+107
-34
lines changed

package.json

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,6 @@
170170
"icon": "$(graph)",
171171
"category": "flowR"
172172
},
173-
{
174-
"command": "vscode-flowr.dataflow-simplified",
175-
"title": "Show Simplified Dataflow Graph",
176-
"icon": "$(graph)",
177-
"category": "flowR"
178-
},
179173
{
180174
"command": "vscode-flowr.session.internal",
181175
"title": "Start Local flowR Shell",
@@ -624,10 +618,15 @@
624618
"Simplification passes that should be applied to the cfg diagram"
625619
]
626620
},
627-
"vscode-flowr.diagram.simplify": {
621+
"vscode-flowr.diagram.simplifyCfg": {
628622
"type": "boolean",
629623
"default": true,
630624
"markdownDescription": "If set to true, cfg diagrams are simplified"
625+
},
626+
"vscode-flowr.diagram.simplifyDfg": {
627+
"type": "boolean",
628+
"default": true,
629+
"markdownDescription": "If set to true, dfg diagrams are simplified"
631630
}
632631
}
633632
},
@@ -823,4 +822,4 @@
823822
"tsdoc/syntax": "off"
824823
}
825824
}
826-
}
825+
}

src/diagram.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ export function registerDiagramCommands(context: vscode.ExtensionContext, output
2222
const activeEditor = vscode.window.activeTextEditor;
2323
return await coordinator.createDiagramPanel(FlowrDiagramType.Dataflow, activeEditor);
2424
});
25-
registerCommand(context, 'vscode-flowr.dataflow-simplified', async() => {
26-
const activeEditor = vscode.window.activeTextEditor;
27-
return await coordinator.createDiagramPanel(FlowrDiagramType.Dataflow, activeEditor, true);
28-
});
2925
registerCommand(context, 'vscode-flowr.ast', async() => {
3026
const activeEditor = vscode.window.activeTextEditor;
3127
return await coordinator.createDiagramPanel(FlowrDiagramType.Ast, activeEditor);
@@ -43,10 +39,9 @@ enum FlowrDiagramType {
4339
}
4440

4541
interface DiagramPanelInformation {
46-
type: FlowrDiagramType;
47-
panel: vscode.WebviewPanel;
48-
simplify: boolean;
49-
options: typeof DefaultDiagramOptions;
42+
type: FlowrDiagramType;
43+
panel: vscode.WebviewPanel;
44+
options: typeof DefaultDiagramOptions;
5045
}
5146

5247
interface ContentUpdateMessage {
@@ -78,21 +73,21 @@ class DiagramUpdateCoordinator {
7873
this.output = output;
7974
}
8075

81-
public async createDiagramPanel(type: FlowrDiagramType, editor: vscode.TextEditor | undefined, simplify: boolean = false) {
76+
public async createDiagramPanel(type: FlowrDiagramType, editor: vscode.TextEditor | undefined) {
8277
if(!editor) {
8378
return;
8479
}
8580

8681
const title = `${nameFromDiagramType(type)} (${path.basename(editor.document.fileName)})`;
8782
const options = optionsFromDiagramType(type);
88-
const mermaid = await diagramFromTypeAndEditor(type, editor, simplify, options);
83+
const mermaid = await diagramFromTypeAndEditor(type, editor, options);
8984
const panel = createDiagramWebview(type as string, title, mermaid, this.output, options);
9085

9186
if(!panel) {
9287
return undefined;
9388
}
9489

95-
const info = { type, panel, simplify, options } satisfies DiagramPanelInformation;
90+
const info = { type, panel, options } satisfies DiagramPanelInformation;
9691

9792
// Stop tracking panel when user closes it
9893
panel.onDidDispose(() => {
@@ -102,7 +97,6 @@ class DiagramUpdateCoordinator {
10297
// Handle settings update messages from panel
10398
panel.webview.onDidReceiveMessage((msg: WebviewMessage) => {
10499
const key = `${DiagramSettingsPrefix}.${msg.key}`;
105-
console.log(`Update: ${JSON.stringify(msg)}`);
106100
if(msg.keyInSet) { // If setKey is set, the checkboxes are grouped into an array
107101
const current = new Set(getConfig().get<string[]>(key, []));
108102
if(msg.value) {
@@ -157,7 +151,7 @@ class DiagramUpdateCoordinator {
157151
}
158152

159153
public async updateWebviewPanel(info: DiagramPanelInformation, textEditor: vscode.TextEditor) {
160-
const mermaid = await diagramFromTypeAndEditor(info.type, textEditor, info.simplify, info.options);
154+
const mermaid = await diagramFromTypeAndEditor(info.type, textEditor, info.options);
161155
info.panel.webview.postMessage({
162156
type: 'content_update',
163157
content: mermaid
@@ -194,12 +188,24 @@ const DefaultDiagramOptions = {
194188
} as DiagramOptionsCheckbox,
195189
} satisfies DiagramOptions;
196190

191+
const DFGDiagramOptions = {
192+
// Default options for mode and sync
193+
...DefaultDiagramOptions,
194+
simplifyDfg: {
195+
type: 'checkbox',
196+
key: DiagramSettingsKeys.SimplifyDfg,
197+
displayText: 'Simplify',
198+
default: true,
199+
currentValue: true
200+
} as DiagramOptionsCheckbox,
201+
} satisfies DiagramOptions;
202+
197203
const CFGDiagramOptions = {
198204
// Default options for mode and sync
199205
...DefaultDiagramOptions,
200-
simplify: {
206+
simplifyCfg: {
201207
type: 'checkbox',
202-
key: DiagramSettingsKeys.Simplify,
208+
key: DiagramSettingsKeys.SimplifyCfg,
203209
displayText: 'Simplify',
204210
default: true,
205211
currentValue: true
@@ -220,7 +226,7 @@ function optionsFromDiagramType(type: FlowrDiagramType) {
220226

221227
switch(type) {
222228
case FlowrDiagramType.Dataflow:
223-
options = DefaultDiagramOptions;
229+
options = DFGDiagramOptions;
224230
break;
225231
case FlowrDiagramType.Controlflow:
226232
options = CFGDiagramOptions;
@@ -259,13 +265,16 @@ function simplificationPassesFromOptions(options: DiagramOptions): CfgSimplifica
259265
return passes;
260266
}
261267

262-
async function diagramFromTypeAndEditor(type: FlowrDiagramType, editor: vscode.TextEditor, simplified: boolean, options: typeof DefaultDiagramOptions): Promise<string> {
268+
async function diagramFromTypeAndEditor(type: FlowrDiagramType, editor: vscode.TextEditor, options: typeof DefaultDiagramOptions): Promise<string> {
263269
const session = await getFlowrSession();
264270
switch(type) {
265-
case FlowrDiagramType.Dataflow: return await session.retrieveDataflowMermaid(editor.document, editor.selections, options.mode.currentValue, simplified);
271+
case FlowrDiagramType.Dataflow: {
272+
const opts = options as typeof DFGDiagramOptions;
273+
return await session.retrieveDataflowMermaid(editor.document, editor.selections, options.mode.currentValue, opts.simplifyDfg.currentValue);
274+
}
266275
case FlowrDiagramType.Controlflow: {
267276
const opts = options as typeof CFGDiagramOptions;
268-
return await session.retrieveCfgMermaid(editor.document, editor.selections, opts.mode.currentValue, opts.simplify.currentValue, simplificationPassesFromOptions(opts));
277+
return await session.retrieveCfgMermaid(editor.document, editor.selections, opts.mode.currentValue, opts.simplifyCfg.currentValue, simplificationPassesFromOptions(opts));
269278
}
270279
case FlowrDiagramType.Ast: return await session.retrieveAstMermaid(editor.document, editor.selections, options.mode.currentValue);
271280
default: assert(false);

src/flowr/internal-session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ export class FlowrInternalSession implements FlowrSession {
261261

262262
return normalizedAstToMermaid(result.ast, {
263263
includeOnlyIds: selectionMode === 'hide' ? selectionNodes : undefined,
264-
mark: selectionMode === 'highlight' ? new Set(selectionNodes?.values().map(v => String(v))) : undefined,
264+
mark: selectionMode === 'highlight' ? new Set(selectionNodes) : undefined,
265265
});
266266
}, 'ast', true, '');
267267
}

src/flowr/utils.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { visitAst } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/process
1111
import type { RNode } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/model';
1212
import type { DiagramSelectionMode } from '../diagram';
1313
import type { CfgSimplificationPassName } from '@eagleoutice/flowr/control-flow/cfg-simplification';
14+
import { RType } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/type';
15+
import type { RExpressionList } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/nodes/r-expression-list';
1416

1517
// Contains utility functions and a common interface for the two FlowrSession implementations
1618

@@ -89,24 +91,45 @@ export function rangeToVscodeRange(range: SourceRange): vscode.Range {
8991
return new vscode.Range(range[0] - 1, range[1] - 1, range[2] - 1, range[3]);
9092
}
9193

92-
export function selectionsToNodeIds(root: (RNode<ParentInformation> | RNode<ParentInformation>[]), selections: readonly vscode.Selection[]): ReadonlySet<NodeId> | undefined {
93-
if(selections.length === 0 || selections[0].isEmpty) {
94+
export function selectionsToNodeIds(root: (RNode<ParentInformation> | RNode<ParentInformation>[]), selectionsRaw: readonly vscode.Selection[]): ReadonlySet<NodeId> | undefined {
95+
if(selectionsRaw.length === 0 || selectionsRaw[0].isEmpty) {
9496
return undefined;
9597
}
9698

9799
const result = new Set<NodeId>();
98-
100+
const maybeIncluded = new Array<RExpressionList<ParentInformation>>();
101+
102+
// By default the end of the selection extends one more coloumn
103+
// Thus we subtract one so that the selection really only includes the selected chars
104+
const selections = selectionsRaw.map(sel => sel.with(
105+
sel.start,
106+
sel.end.with(sel.end.line, Math.max(sel.end.character - 1, 0))
107+
));
108+
99109
visitAst(root, node => {
100-
if(!node.info.fullRange) {
110+
if(node.type === RType.ExpressionList) {
111+
maybeIncluded.push(node);
101112
return;
102113
}
103114

104-
const range = rangeToVscodeRange(node.info.fullRange);
115+
const location = node.location ?? node.info.fullRange;
116+
if(location === undefined) {
117+
return;
118+
}
119+
120+
const range = rangeToVscodeRange(location);
105121
if(selections.some(sel => sel.intersection(range) !== undefined)) {
106122
result.add(node.info.id);
107123
}
108124
});
109125

126+
for(const maybe of maybeIncluded) {
127+
const shouldBeIncluded = maybe.children.length !== 0 && maybe.children.every(c => result.has(c.info.id));
128+
if(shouldBeIncluded) {
129+
result.add(maybe.info.id);
130+
}
131+
}
132+
110133
return result;
111134
}
112135

src/settings.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ export enum DiagramSettingsKeys {
6666
Sync = 'sync',
6767
Mode = 'mode',
6868
SimplificationPasses = 'cfgSimplificationPasses',
69-
Simplify = 'simplify'
69+
SimplifyCfg = 'simplifyCfg',
70+
SimplifyDfg = 'simplifyDfg'
71+
7072
}
7173

7274
export const DiagramSettingsPrefix = 'diagram';

src/test/utils.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { FlowrInlineTextFile } from '@eagleoutice/flowr/project/context/flowr-file';
2+
import { FlowrAnalyzerBuilder } from '@eagleoutice/flowr/project/flowr-analyzer-builder';
3+
import { describe, it, suite } from 'mocha';
4+
import { selectionsToNodeIds } from '../flowr/utils';
5+
import { FlowrAnalyzerPluginDefaults } from '@eagleoutice/flowr/project/plugins/flowr-analyzer-plugin-defaults';
6+
import assert from 'assert';
7+
import * as vscode from 'vscode';
8+
9+
10+
suite('util', () => {
11+
describe('selection to node ids', () => {
12+
const cases = [
13+
{ selections: [], expected: undefined },
14+
{ selections: [new vscode.Selection(0, 0, 0, 2)], expected: [0] },
15+
{ selections: [new vscode.Selection(0, 0, 0, 3)], expected: [2, 0] },
16+
{ selections: [new vscode.Selection(0, 0, 0, 3), new vscode.Selection(1, 0, 1, 3),], expected: [2, 0, 5, 3] },
17+
18+
];
19+
20+
21+
cases.forEach(({ selections, expected }) => {
22+
it(`should only include selected nodeids ${expected?.join(', ') ?? '(none)'}`, async() => {
23+
const analyzer = await new FlowrAnalyzerBuilder()
24+
.setEngine('tree-sitter')
25+
.unregisterPlugins(...FlowrAnalyzerPluginDefaults())
26+
.build();
27+
28+
analyzer.addFile(new FlowrInlineTextFile('a.R', `x <- 5
29+
y <- 10
30+
z <- 23
31+
`));
32+
analyzer.addRequest({ request: 'file', content: 'a.R' });
33+
34+
const ast = await analyzer.normalize();
35+
const actual = selectionsToNodeIds(ast.ast.files.map(a => a.root), selections);
36+
assert.deepEqual(actual?.values().toArray(), expected);
37+
});
38+
});
39+
});
40+
});

0 commit comments

Comments
 (0)