diff --git a/src/extension.ts b/src/extension.ts index e7e8e9e..300d89c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,6 +13,7 @@ import type { FlowrConfigOptions } from '@eagleoutice/flowr/config'; import { DropPathsOption, InferWorkingDirectory, 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; @@ -95,6 +96,15 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(new vscode.Disposable(() => destroySession())); + const { dispose: disposeDep, update: updateDependencyView } = registerDependencyView(outputChannel); + + context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.dependencyView.update', () => { + updateDependencyView(); + })); + + context.subscriptions.push(registerInlineHints(outputChannel)) + + context.subscriptions.push(new vscode.Disposable(() => disposeDep())); setTimeout(() => { const { dispose: disposeDep, update: updateDependencyView } = registerDependencyView(outputChannel); context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.dependencyView.update', () => { 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 new file mode 100644 index 0000000..9aa8cca --- /dev/null +++ b/src/flowr/views/inline-values.ts @@ -0,0 +1,148 @@ +import * as vscode from 'vscode'; +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( + // 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 graphInfo: DataflowGraph | undefined; + private normalizeInfo: NormalizedAst | 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 { + const active = vscode.window.activeTextEditor; + if(!active) { + return; + } + const content = active.document.getText(); + if(content.trim() === this.lastEditorContent) { + return; + } + this.lastEditorContent = content.trim(); + + + 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.graphInfo) { + return new Set(); + } + const variables = new Set(); + for(const [v,info] of this.graphInfo.vertices(true)) { + if(info.tag === VertexType.Use) { + variables.add(v); + } + } + return variables; + } + + private getValuesForVariable(variable: NodeId): string[] { + if(!this.graphInfo || !this.normalizeInfo) { + return []; + } + 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.graphInfo || !this.normalizeInfo) { + 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.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}`, + tooltip: 'Values: ' + vals, + kind: vscode.InlayHintKind.Parameter, + position, + 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