From d8696d5e735ea12ea421e854bcf979cf919af444 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Wed, 19 Feb 2025 10:14:11 +0100 Subject: [PATCH 1/2] wip: working on resolve values --- src/extension.ts | 5 +- src/flowr/views/inline-values.ts | 144 +++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 src/flowr/views/inline-values.ts diff --git a/src/extension.ts b/src/extension.ts index 3b96dec..e3769f5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,6 +12,7 @@ import { registerDependencyView } from './flowr/views/dependency-view'; import { VariableResolve , defaultConfigOptions, setConfig } from '@eagleoutice/flowr/config'; import type { BuiltInDefinitions } from '@eagleoutice/flowr/dataflow/environments/built-in-config'; import { deepMergeObject } from '@eagleoutice/flowr/util/objects'; +import { registerInlineHints } from './flowr/views/inline-values'; export const MINIMUM_R_MAJOR = 3; export const BEST_R_MAJOR = 4; @@ -77,6 +78,8 @@ export async function activate(context: vscode.ExtensionContext) { updateDependencyView(); })); + context.subscriptions.push(registerInlineHints(outputChannel)) + context.subscriptions.push(new vscode.Disposable(() => disposeDep())); process.on('SIGINT', () => destroySession()); @@ -195,7 +198,7 @@ function updateFlowrConfig() { const config = getConfig(); const wasmRoot = getWasmRootPath(); // we don't want to *amend* here since updates to our extension config shouldn't add additional entries while keeping old ones (definitions etc.) - setConfig(deepMergeObject(defaultConfigOptions, { + setConfig(deepMergeObject(defaultConfigOptions, { ignoreSourceCalls: config.get(Settings.IgnoreSourceCalls, false), solver: { variables: config.get(Settings.SolverVariableHandling, VariableResolve.Alias), diff --git a/src/flowr/views/inline-values.ts b/src/flowr/views/inline-values.ts new file mode 100644 index 0000000..6e37c9c --- /dev/null +++ b/src/flowr/views/inline-values.ts @@ -0,0 +1,144 @@ +import * as vscode from 'vscode'; +import { PipelineOutput } from '@eagleoutice/flowr/core/steps/pipeline/pipeline'; +import { TREE_SITTER_DATAFLOW_PIPELINE, createDataflowPipeline } from '@eagleoutice/flowr/core/steps/pipeline/default-pipelines'; +import { TreeSitterExecutor } from '@eagleoutice/flowr/r-bridge/lang-4.x/tree-sitter/tree-sitter-executor'; +import { requestFromInput } from '@eagleoutice/flowr/r-bridge/retriever'; +import { NodeId } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/processing/node-id'; +import { VertexType } from '@eagleoutice/flowr/dataflow/graph/vertex'; +import { resolve } from '@eagleoutice/flowr/dataflow/environments/resolve-by-name'; +import { RLogicalValue } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/nodes/r-logical'; +import { RNumberValue, RStringValue } from '@eagleoutice/flowr/r-bridge/lang-4.x/convert-values'; + +export function registerInlineHints(output: vscode.OutputChannel): vscode.Disposable { + return vscode.languages.registerInlayHintsProvider( + // only for r + { scheme: 'file', language: 'r' }, + new FlowrInlayHintsProvider(output) + ) +} + +class FlowrInlayHintsProvider implements vscode.InlayHintsProvider { + private readonly output: vscode.OutputChannel; + private readonly updateEvent = new vscode.EventEmitter(); + public onDidChangeInlayHints = this.updateEvent.event; + + // TODO: work with the server as well + // TODO: merge infrastructure with dependency viewer? + private analysisInfo: PipelineOutput | undefined; + // TODO: on update event etc. + + constructor(output: vscode.OutputChannel) { + this.output = output; + // TODO: register disposables + vscode.workspace.onDidChangeTextDocument(e => { + if(e.document.languageId === 'r') { + void this.update(); + } + }) + vscode.window.onDidChangeActiveTextEditor(e => { + if(e?.document.languageId === 'r') { + void this.update(); + } + }) + setTimeout(() => void this.update(), 50); + setTimeout(() => void this.update(), 250); + } + + private lastEditorContent: string | undefined; + async update(): Promise { + this.output.appendLine('Updating inlay hints'); + const active = vscode.window.activeTextEditor; + if(!active) { + return; + } + const content = active.document.getText(); + if(content.trim() === this.lastEditorContent) { + return; + } + this.lastEditorContent = content.trim(); + + this.analysisInfo = await createDataflowPipeline(new TreeSitterExecutor(), { + request: requestFromInput(content) + }).allRemainingSteps(); + this.updateEvent.fire(); + } + + private collectAllVariables(): Set { + if(!this.analysisInfo) { + return new Set(); + } + const variables = new Set(); + for(const [v,info] of this.analysisInfo.dataflow.graph.vertices(true)) { + if(info.tag === VertexType.Use) { + variables.add(v); + } + } + return variables; + } + + private getValuesForVariable(variable: NodeId): string[] { + if(!this.analysisInfo) { + return []; + } + const values = resolve(variable, { graph: this.analysisInfo.dataflow.graph, full: true, idMap: this.analysisInfo.normalize.idMap }); + + return values?.map(unwrapRValue).filter(isNotUndefined) ?? []; + } + + provideInlayHints(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): vscode.ProviderResult { + if(!this.analysisInfo) { + return []; + } + // TODO: respect hints + const variables = [...this.collectAllVariables()].map(v => [v, this.getValuesForVariable(v)] as const); + const results: vscode.InlayHint[] = []; + + for(const [variable, values] of variables) { + if(values.length === 0) { + continue; + } + const loc = this.analysisInfo.normalize.idMap.get(variable); + if(!loc?.location) { + continue; + } + const vals = values.join(' | '); + results.push({ + label: `: ${vals}`, + kind: vscode.InlayHintKind.Type, + position: new vscode.Position(loc.location[2], loc.location[3] - 1), + paddingLeft: true + }) + } + + return results; + } +} + +// maybe take from flowR +function unwrapRValue(value: RLogicalValue | RStringValue | RNumberValue | string | number | unknown): string | undefined { + if(value === undefined) { + return undefined; + } + switch(typeof value) { + case 'string': + return value; + case 'number': + return value.toString(); + case 'boolean': + return value ? 'TRUE' : 'FALSE'; + } + if(typeof value !== 'object' || value === null) { + return JSON.stringify(value); + } + if('str' in value) { + return (value as RStringValue).str; + } else if('num' in value) { + return (value as RNumberValue).num.toString(); + } else { + return JSON.stringify(value); + } +} + +function isNotUndefined(value: T | undefined): value is T { + return value !== undefined; +} \ No newline at end of file From b48ef904a3db2ce8dae56635b3418445fd0ba6e0 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Wed, 28 May 2025 22:22:00 +0200 Subject: [PATCH 2/2] feat: improve on resolve value of variable --- src/flowr/utils.ts | 2 +- src/flowr/views/inline-values.ts | 42 +++++++++++++++++--------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/flowr/utils.ts b/src/flowr/utils.ts index 4b8a8a6..d760c3e 100644 --- a/src/flowr/utils.ts +++ b/src/flowr/utils.ts @@ -27,7 +27,7 @@ export interface FlowrSession { retrieveDataflowMermaid: (document: vscode.TextDocument, simplified?: boolean) => Promise retrieveAstMermaid: (document: vscode.TextDocument) => Promise retrieveCfgMermaid: (document: vscode.TextDocument) => Promise - retrieveQuery: (document: vscode.TextDocument, query: Queries) => Promise<{ result: QueryResults, hasError: boolean, dfg?: DataflowGraph, ast?: NormalizedAst }> + retrieveQuery: (document: vscode.TextDocument, query: Queries) => Promise<{ result: QueryResults, hasError: boolean, dfg?: DataflowGraph, ast?: NormalizedAst }> runRepl: (output: Omit) => Promise } diff --git a/src/flowr/views/inline-values.ts b/src/flowr/views/inline-values.ts index 8d5bca9..9aa8cca 100644 --- a/src/flowr/views/inline-values.ts +++ b/src/flowr/views/inline-values.ts @@ -1,13 +1,12 @@ import * as vscode from 'vscode'; -import { PipelineOutput } from '@eagleoutice/flowr/core/steps/pipeline/pipeline'; -import { TREE_SITTER_DATAFLOW_PIPELINE, createDataflowPipeline } from '@eagleoutice/flowr/core/steps/pipeline/default-pipelines'; -import { TreeSitterExecutor } from '@eagleoutice/flowr/r-bridge/lang-4.x/tree-sitter/tree-sitter-executor'; -import { requestFromInput } from '@eagleoutice/flowr/r-bridge/retriever'; import { NodeId } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/processing/node-id'; import { VertexType } from '@eagleoutice/flowr/dataflow/graph/vertex'; import { resolveIdToValue } from '@eagleoutice/flowr/dataflow/environments/resolve-by-name'; import { RLogicalValue } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/nodes/r-logical'; import { RNumberValue, RStringValue } from '@eagleoutice/flowr/r-bridge/lang-4.x/convert-values'; +import { getFlowrSession } from '../../extension'; +import { DataflowGraph } from '@eagleoutice/flowr/dataflow/graph/graph'; +import { NormalizedAst } from '@eagleoutice/flowr/r-bridge/lang-4.x/ast/model/processing/decorate'; export function registerInlineHints(output: vscode.OutputChannel): vscode.Disposable { return vscode.languages.registerInlayHintsProvider( @@ -24,7 +23,8 @@ class FlowrInlayHintsProvider implements vscode.InlayHintsProvider { // TODO: work with the server as well // TODO: merge infrastructure with dependency viewer? - private analysisInfo: PipelineOutput | undefined; + private graphInfo: DataflowGraph | undefined; + private normalizeInfo: NormalizedAst | undefined; // TODO: on update event etc. constructor(output: vscode.OutputChannel) { @@ -46,7 +46,6 @@ class FlowrInlayHintsProvider implements vscode.InlayHintsProvider { private lastEditorContent: string | undefined; async update(): Promise { - this.output.appendLine('Updating inlay hints'); const active = vscode.window.activeTextEditor; if(!active) { return; @@ -57,18 +56,22 @@ class FlowrInlayHintsProvider implements vscode.InlayHintsProvider { } this.lastEditorContent = content.trim(); - this.analysisInfo = await createDataflowPipeline(new TreeSitterExecutor(), { - request: requestFromInput(content) - }).allRemainingSteps(); + + const session = await getFlowrSession(); + + const res = (await session.retrieveQuery(active.document, [{ type: 'dataflow' }, { type: 'normalized-ast' }])); + this.graphInfo = res.result.dataflow.graph; + this.normalizeInfo = res.result['normalized-ast'].normalized; + this.updateEvent.fire(); } private collectAllVariables(): Set { - if(!this.analysisInfo) { + if(!this.graphInfo) { return new Set(); } const variables = new Set(); - for(const [v,info] of this.analysisInfo.dataflow.graph.vertices(true)) { + for(const [v,info] of this.graphInfo.vertices(true)) { if(info.tag === VertexType.Use) { variables.add(v); } @@ -77,16 +80,15 @@ class FlowrInlayHintsProvider implements vscode.InlayHintsProvider { } private getValuesForVariable(variable: NodeId): string[] { - if(!this.analysisInfo) { + if(!this.graphInfo || !this.normalizeInfo) { return []; } - const values = resolveIdToValue(variable, { graph: this.analysisInfo.dataflow.graph, full: true, idMap: this.analysisInfo.normalize.idMap }); - + const values = resolveIdToValue(variable, { graph: this.graphInfo, full: true, idMap: this.normalizeInfo.idMap }); return values?.map(unwrapRValue).filter(isNotUndefined) ?? []; } provideInlayHints(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): vscode.ProviderResult { - if(!this.analysisInfo) { + if(!this.graphInfo || !this.normalizeInfo) { return []; } // TODO: respect hints @@ -97,16 +99,18 @@ class FlowrInlayHintsProvider implements vscode.InlayHintsProvider { if(values.length === 0) { continue; } - const loc = this.analysisInfo.normalize.idMap.get(variable); + const loc = this.normalizeInfo.idMap?.get(variable); if(!loc?.location) { continue; } const vals = values.join(' | '); + const position = new vscode.Position(loc.location[0] - 1, loc.location[1]); results.push({ label: `: ${vals}`, - kind: vscode.InlayHintKind.Type, - position: new vscode.Position(loc.location[2], loc.location[3] - 1), - paddingLeft: true + tooltip: 'Values: ' + vals, + kind: vscode.InlayHintKind.Parameter, + position, + paddingLeft: true }) }