Skip to content

Commit 75d315a

Browse files
randy3ktomtom
andcommitted
feat: evaluate params from YAML header in Rmd files before running code
Ported changes from PR #1693 to dev branch. Prepend params command when running selection or chunks from Rmd files with params in YAML frontmatter. Co-authored-by: tomtom <tomtom@users.noreply.github.com>
1 parent 310defe commit 75d315a

1 file changed

Lines changed: 71 additions & 3 deletions

File tree

‎src/rTerminal.ts‎

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,74 @@ import { cleanupSession } from './session';
1313
import { config, delay, getRterm, getCurrentWorkspaceFolder } from './util';
1414
import { rGuestService, isGuestSession } from './liveShare';
1515
import * as fs from 'fs';
16+
import * as yaml from 'js-yaml';
17+
1618
export let rTerm: vscode.Terminal | undefined = undefined;
1719

20+
let lastParamsRmdPath: string | undefined;
21+
let lastParamsRmdVersion: number | undefined;
22+
23+
const rExprType = new yaml.Type('!r', {
24+
kind: 'scalar',
25+
construct: (data: string) => ({ __rExpr: data }),
26+
});
27+
const RMARKDOWN_SCHEMA = yaml.DEFAULT_SCHEMA.extend([rExprType]);
28+
29+
function valueToR(val: unknown): string {
30+
if (val === null || val === undefined) {
31+
return 'NULL';
32+
}
33+
if (typeof val === 'boolean') {
34+
return val ? 'TRUE' : 'FALSE';
35+
}
36+
if (typeof val === 'number') {
37+
return String(val);
38+
}
39+
if (typeof val === 'string') {
40+
return `"${val.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
41+
}
42+
if (typeof val === 'object' && val !== null && '__rExpr' in (val as Record<string, unknown>)) {
43+
return (val as { __rExpr: string }).__rExpr;
44+
}
45+
if (Array.isArray(val)) {
46+
return `c(${val.map(valueToR).join(', ')})`;
47+
}
48+
const obj = val as Record<string, unknown>;
49+
if ('value' in obj) {
50+
return valueToR(obj['value']);
51+
}
52+
const entries = Object.entries(obj).map(([k, v]) => `${k} = ${valueToR(v)}`);
53+
return `list(${entries.join(', ')})`;
54+
}
55+
56+
export function getRmdParamsCommand(document: vscode.TextDocument): string | undefined {
57+
if (document.languageId !== 'rmd') {
58+
return undefined;
59+
}
60+
const text = document.getText();
61+
const match = text.match(/^---\s*\n([\s\S]*?)\n---/);
62+
if (!match || !/^\s*params\s*:/m.test(match[1])) {
63+
return undefined;
64+
}
65+
const filePath = document.uri.fsPath;
66+
if (filePath === lastParamsRmdPath && document.version === lastParamsRmdVersion) {
67+
return undefined;
68+
}
69+
lastParamsRmdPath = filePath;
70+
lastParamsRmdVersion = document.version;
71+
try {
72+
const frontmatter = yaml.load(match[1], { schema: RMARKDOWN_SCHEMA }) as Record<string, unknown>;
73+
const params = frontmatter?.['params'] as Record<string, unknown> | undefined;
74+
if (!params || typeof params !== 'object') {
75+
return undefined;
76+
}
77+
const entries = Object.entries(params).map(([k, v]) => `${k} = ${valueToR(v)}`);
78+
return `params <- list(${entries.join(', ')})`;
79+
} catch {
80+
return undefined;
81+
}
82+
}
83+
1884
export async function runSource(echo: boolean): Promise<void> {
1985
const wad = vscode.window.activeTextEditor?.document;
2086
if (!wad) {
@@ -234,8 +300,8 @@ export async function runSelectionInTerm(moveCursor: boolean, useRepl = true): P
234300
if (!selection) {
235301
return;
236302
}
303+
const textEditor = vscode.window.activeTextEditor;
237304
if (moveCursor && selection.linesDownToMoveCursor > 0) {
238-
const textEditor = vscode.window.activeTextEditor;
239305
if (!textEditor) {
240306
return;
241307
}
@@ -250,7 +316,8 @@ export async function runSelectionInTerm(moveCursor: boolean, useRepl = true): P
250316
if(useRepl && vscode.debug.activeDebugSession?.type === 'R-Debugger'){
251317
await sendRangeToRepl(selection.range);
252318
} else{
253-
await runTextInTerm(selection.selectedText);
319+
const paramsCmd = textEditor ? getRmdParamsCommand(textEditor.document) : undefined;
320+
await runTextInTerm(paramsCmd ? `${paramsCmd}\n${selection.selectedText}` : selection.selectedText);
254321
}
255322
}
256323

@@ -259,12 +326,13 @@ export async function runChunksInTerm(chunks: vscode.Range[]): Promise<void> {
259326
if (!textEditor) {
260327
return;
261328
}
329+
const paramsCmd = getRmdParamsCommand(textEditor.document);
262330
const text = chunks
263331
.map((chunk) => textEditor.document.getText(chunk).trim())
264332
.filter((chunk) => chunk.length > 0)
265333
.join('\n');
266334
if (text.length > 0) {
267-
return runTextInTerm(text);
335+
return runTextInTerm(paramsCmd ? `${paramsCmd}\n${text}` : text);
268336
}
269337
}
270338

0 commit comments

Comments
 (0)